From 32d08570e7172e4ac8e3be5222a500988afc7381 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:33:58 +0530 Subject: [PATCH 1/3] chore: peek overview for issue view and my issue view (#2172) * chore: peak overview for issue view and my issue view * fix: profile issue peak overview mutation fix * chore: code refactor * fix: image prefix url fix --- space/components/accounts/sign-in.tsx | 2 +- space/pages/onboarding/index.tsx | 2 +- web/components/core/views/all-views.tsx | 16 +- .../core/views/board-view/all-boards.tsx | 34 +++++ .../core/views/board-view/single-board.tsx | 5 +- .../core/views/board-view/single-issue.tsx | 40 +++-- .../core/views/calendar-view/calendar.tsx | 140 ++++++++++-------- .../core/views/calendar-view/single-issue.tsx | 46 +++--- .../core/views/gantt-chart-view/index.tsx | 14 +- web/components/core/views/issues-view.tsx | 3 +- .../core/views/list-view/all-lists.tsx | 33 +++++ .../core/views/list-view/single-issue.tsx | 48 ++++-- .../core/views/list-view/single-list.tsx | 3 + .../gantt-chart/cycle-issues-layout.tsx | 50 ++++--- web/components/issues/form.tsx | 9 +- web/components/issues/gantt-chart/blocks.tsx | 24 ++- web/components/issues/gantt-chart/layout.tsx | 50 ++++--- .../issues/my-issues/my-issues-view.tsx | 2 +- .../gantt-chart/module-issues-layout.tsx | 50 ++++--- web/components/views/gantt-chart.tsx | 48 +++--- web/hooks/use-calendar-issues-view.tsx | 15 +- 21 files changed, 417 insertions(+), 217 deletions(-) diff --git a/space/components/accounts/sign-in.tsx b/space/components/accounts/sign-in.tsx index ed55f7697..d2f1a1206 100644 --- a/space/components/accounts/sign-in.tsx +++ b/space/components/accounts/sign-in.tsx @@ -13,7 +13,7 @@ import useToast from "hooks/use-toast"; // components import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts"; // images -const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : ""; +const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces" : ""; export const SignInView = observer(() => { const { user: userStore } = useMobxStore(); diff --git a/space/pages/onboarding/index.tsx b/space/pages/onboarding/index.tsx index e7ed35222..491146410 100644 --- a/space/pages/onboarding/index.tsx +++ b/space/pages/onboarding/index.tsx @@ -5,7 +5,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { OnBoardingForm } from "components/accounts/onboarding-form"; -const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : ""; +const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces" : ""; const OnBoardingPage = () => { const { user: userStore } = useMobxStore(); diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index 3b95ed863..750c1a552 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import { useRouter } from "next/router"; @@ -77,6 +77,8 @@ export const AllViews: React.FC = ({ const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const [myIssueProjectId, setMyIssueProjectId] = useState(null); + const { user } = useUser(); const { memberRole } = useProjectMyMembership(); @@ -90,6 +92,10 @@ export const AllViews: React.FC = ({ ); const states = getStatesList(stateGroups); + const handleMyIssueOpen = (issue: IIssue) => { + setMyIssueProjectId(issue.project); + }; + const handleTrashBox = useCallback( (isDragging: boolean) => { if (isDragging && !trashBox) setTrashBox(true); @@ -128,6 +134,8 @@ export const AllViews: React.FC = ({ handleIssueAction={handleIssueAction} openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null} removeIssue={removeIssue} + myIssueProjectId={myIssueProjectId} + handleMyIssueOpen={handleMyIssueOpen} disableUserActions={disableUserActions} disableAddIssueOption={disableAddIssueOption} user={user} @@ -143,6 +151,8 @@ export const AllViews: React.FC = ({ handleIssueAction={handleIssueAction} handleTrashBox={handleTrashBox} openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null} + myIssueProjectId={myIssueProjectId} + handleMyIssueOpen={handleMyIssueOpen} removeIssue={removeIssue} states={states} user={user} @@ -166,7 +176,9 @@ export const AllViews: React.FC = ({ userAuth={memberRole} /> ) : ( - displayFilters?.layout === "gantt_chart" && + displayFilters?.layout === "gantt_chart" && ( + + ) )} ) : router.pathname.includes("archived-issues") ? ( diff --git a/web/components/core/views/board-view/all-boards.tsx b/web/components/core/views/board-view/all-boards.tsx index 0d5a4534e..ca0dd59a2 100644 --- a/web/components/core/views/board-view/all-boards.tsx +++ b/web/components/core/views/board-view/all-boards.tsx @@ -1,5 +1,12 @@ +import { useRouter } from "next/router"; + +//hook +import useMyIssues from "hooks/my-issues/use-my-issues"; +import useIssuesView from "hooks/use-issues-view"; +import useProfileIssues from "hooks/use-profile-issues"; // components import { SingleBoard } from "components/core/views/board-view/single-board"; +import { IssuePeekOverview } from "components/issues"; // icons import { StateGroupIcon } from "components/icons"; // helpers @@ -16,6 +23,8 @@ type Props = { handleTrashBox: (isDragging: boolean) => void; openIssuesListModal?: (() => void) | null; removeIssue: ((bridgeId: string, issueId: string) => void) | null; + myIssueProjectId?: string | null; + handleMyIssueOpen?: (issue: IIssue) => void; states: IState[] | undefined; user: ICurrentUserResponse | undefined; userAuth: UserAuth; @@ -30,16 +39,40 @@ export const AllBoards: React.FC = ({ handleIssueAction, handleTrashBox, openIssuesListModal, + myIssueProjectId, + handleMyIssueOpen, removeIssue, states, user, userAuth, viewProps, }) => { + const router = useRouter(); + const { workspaceSlug, projectId, userId } = router.query; + + const isProfileIssue = + router.pathname.includes("assigned") || + router.pathname.includes("created") || + router.pathname.includes("subscribed"); + + const isMyIssue = router.pathname.includes("my-issues"); + + const { mutateIssues } = useIssuesView(); + const { mutateMyIssues } = useMyIssues(workspaceSlug?.toString()); + const { mutateProfileIssues } = useProfileIssues(workspaceSlug?.toString(), userId?.toString()); + const { displayFilters, groupedIssues } = viewProps; return ( <> + + isMyIssue ? mutateMyIssues() : isProfileIssue ? mutateProfileIssues() : mutateIssues() + } + projectId={myIssueProjectId ? myIssueProjectId : projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} + /> {groupedIssues ? (
{Object.keys(groupedIssues).map((singleGroup, index) => { @@ -63,6 +96,7 @@ export const AllBoards: React.FC = ({ handleIssueAction={handleIssueAction} handleTrashBox={handleTrashBox} openIssuesListModal={openIssuesListModal ?? null} + handleMyIssueOpen={handleMyIssueOpen} removeIssue={removeIssue} user={user} userAuth={userAuth} diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index fcc3a56bf..5b87f8aba 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -26,6 +26,7 @@ type Props = { handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; handleTrashBox: (isDragging: boolean) => void; openIssuesListModal?: (() => void) | null; + handleMyIssueOpen?: (issue: IIssue) => void; removeIssue: ((bridgeId: string, issueId: string) => void) | null; user: ICurrentUserResponse | undefined; userAuth: UserAuth; @@ -42,6 +43,7 @@ export const SingleBoard: React.FC = ({ handleIssueAction, handleTrashBox, openIssuesListModal, + handleMyIssueOpen, removeIssue, user, userAuth, @@ -50,7 +52,7 @@ export const SingleBoard: React.FC = ({ // collapse/expand const [isCollapsed, setIsCollapsed] = useState(true); - const { displayFilters, groupedIssues, properties } = viewProps; + const { displayFilters, groupedIssues } = viewProps; const router = useRouter(); const { cycleId, moduleId } = router.query; @@ -135,6 +137,7 @@ export const SingleBoard: React.FC = ({ makeIssueCopy={() => handleIssueAction(issue, "copy")} handleDeleteIssue={() => handleIssueAction(issue, "delete")} handleTrashBox={handleTrashBox} + handleMyIssueOpen={handleMyIssueOpen} removeIssue={() => { if (removeIssue && issue.bridge_id) removeIssue(issue.bridge_id, issue.id); diff --git a/web/components/core/views/board-view/single-issue.tsx b/web/components/core/views/board-view/single-issue.tsx index dc750babe..ffd4747d9 100644 --- a/web/components/core/views/board-view/single-issue.tsx +++ b/web/components/core/views/board-view/single-issue.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; -import Link from "next/link"; import { useRouter } from "next/router"; import { mutate } from "swr"; @@ -58,6 +57,7 @@ type Props = { index: number; editIssue: () => void; makeIssueCopy: () => void; + handleMyIssueOpen?: (issue: IIssue) => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; handleTrashBox: (isDragging: boolean) => void; @@ -75,6 +75,7 @@ export const SingleBoardIssue: React.FC = ({ index, editIssue, makeIssueCopy, + handleMyIssueOpen, removeIssue, groupTitle, handleDeleteIssue, @@ -187,6 +188,17 @@ export const SingleBoardIssue: React.FC = ({ useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); + const openPeekOverview = () => { + const { query } = router; + + if (handleMyIssueOpen) handleMyIssueOpen(issue); + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue.id }, + }); + }; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; return ( @@ -286,16 +298,22 @@ export const SingleBoardIssue: React.FC = ({ )}
)} - - - {properties.key && ( -
- {issue.project_detail.identifier}-{issue.sequence_id} -
- )} -
{issue.name}
-
- + +
+ {properties.key && ( +
+ {issue.project_detail.identifier}-{issue.sequence_id} +
+ )} + +
+
= ({ const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; - const { calendarIssues, params, displayFilters, setDisplayFilters } = useCalendarIssuesView(); + const { calendarIssues, mutateIssues, params, displayFilters, setDisplayFilters } = + useCalendarIssuesView(); const totalDate = eachDayOfInterval({ start: calendarDates.startDate, @@ -170,75 +172,85 @@ export const CalendarView: React.FC = ({ const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; - return calendarIssues ? ( -
- -
- + return ( + <> + mutateIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} + /> + {calendarIssues ? ( +
+ +
+ -
- {weeks.map((date, index) => (
- - {isMonthlyView - ? formatDate(date, "eee").substring(0, 3) - : formatDate(date, "eee")} - - {!isMonthlyView && {formatDate(date, "d")}} + {weeks.map((date, index) => ( +
+ + {isMonthlyView + ? formatDate(date, "eee").substring(0, 3) + : formatDate(date, "eee")} + + {!isMonthlyView && {formatDate(date, "d")}} +
+ ))}
- ))} -
-
- {currentViewDaysData.map((date, index) => ( - - ))} -
+
+ {currentViewDaysData.map((date, index) => ( + + ))} +
+
+
- -
- ) : ( -
- -
+ ) : ( +
+ +
+ )} + ); }; diff --git a/web/components/core/views/calendar-view/single-issue.tsx b/web/components/core/views/calendar-view/single-issue.tsx index f6c1cc2f7..3db571c99 100644 --- a/web/components/core/views/calendar-view/single-issue.tsx +++ b/web/components/core/views/calendar-view/single-issue.tsx @@ -1,6 +1,5 @@ import React, { useCallback } from "react"; -import Link from "next/link"; import { useRouter } from "next/router"; import { mutate } from "swr"; @@ -158,6 +157,15 @@ export const SingleCalendarIssue: React.FC = ({ ? Object.values(properties).some((value) => value === true) : false; + const openPeekOverview = () => { + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue.id }, + }); + }; + return (
= ({
)} - - - {properties.key && ( - - - {issue.project_detail?.identifier}-{issue.sequence_id} - - - )} - - {truncateText(issue.name, 25)} + + + {displayProperties && (
{properties.priority && ( diff --git a/web/components/core/views/gantt-chart-view/index.tsx b/web/components/core/views/gantt-chart-view/index.tsx index a881cb7aa..2cd10f95f 100644 --- a/web/components/core/views/gantt-chart-view/index.tsx +++ b/web/components/core/views/gantt-chart-view/index.tsx @@ -6,20 +6,24 @@ import { IssueGanttChartView } from "components/issues"; import { ModuleIssuesGanttChartView } from "components/modules"; import { ViewIssuesGanttChartView } from "components/views"; -export const GanttChartView = () => { +type Props = { + disableUserActions: boolean; +}; + +export const GanttChartView: React.FC = ({ disableUserActions }) => { const router = useRouter(); const { cycleId, moduleId, viewId } = router.query; return ( <> {cycleId ? ( - + ) : moduleId ? ( - + ) : viewId ? ( - + ) : ( - + )} ); diff --git a/web/components/core/views/issues-view.tsx b/web/components/core/views/issues-view.tsx index b4dd665dd..e0e7e8c94 100644 --- a/web/components/core/views/issues-view.tsx +++ b/web/components/core/views/issues-view.tsx @@ -19,7 +19,7 @@ import useIssuesProperties from "hooks/use-issue-properties"; import useProjectMembers from "hooks/use-project-members"; // components import { FiltersList, AllViews } from "components/core"; -import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; +import { CreateUpdateIssueModal, DeleteIssueModal, IssuePeekOverview } from "components/issues"; import { CreateUpdateViewModal } from "components/views"; // ui import { PrimaryButton, SecondaryButton } from "components/ui"; @@ -462,6 +462,7 @@ export const IssuesView: React.FC = ({ data={issueToDelete} user={user} /> + {areFiltersApplied && ( <>
diff --git a/web/components/core/views/list-view/all-lists.tsx b/web/components/core/views/list-view/all-lists.tsx index 282e27755..bb0a7c0fb 100644 --- a/web/components/core/views/list-view/all-lists.tsx +++ b/web/components/core/views/list-view/all-lists.tsx @@ -1,5 +1,12 @@ +import { useRouter } from "next/router"; + +// hooks +import useMyIssues from "hooks/my-issues/use-my-issues"; +import useIssuesView from "hooks/use-issues-view"; +import useProfileIssues from "hooks/use-profile-issues"; // components import { SingleList } from "components/core/views/list-view/single-list"; +import { IssuePeekOverview } from "components/issues"; // types import { ICurrentUserResponse, IIssue, IIssueViewProps, IState, UserAuth } from "types"; @@ -9,6 +16,8 @@ type Props = { addIssueToGroup: (groupTitle: string) => void; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; openIssuesListModal?: (() => void) | null; + myIssueProjectId?: string | null; + handleMyIssueOpen?: (issue: IIssue) => void; removeIssue: ((bridgeId: string, issueId: string) => void) | null; disableUserActions: boolean; disableAddIssueOption?: boolean; @@ -23,16 +32,39 @@ export const AllLists: React.FC = ({ disableUserActions, disableAddIssueOption = false, openIssuesListModal, + handleMyIssueOpen, + myIssueProjectId, removeIssue, states, user, userAuth, viewProps, }) => { + const router = useRouter(); + const { workspaceSlug, projectId, userId } = router.query; + + const isProfileIssue = + router.pathname.includes("assigned") || + router.pathname.includes("created") || + router.pathname.includes("subscribed"); + + const isMyIssue = router.pathname.includes("my-issues"); + const { mutateIssues } = useIssuesView(); + const { mutateMyIssues } = useMyIssues(workspaceSlug?.toString()); + const { mutateProfileIssues } = useProfileIssues(workspaceSlug?.toString(), userId?.toString()); + const { displayFilters, groupedIssues } = viewProps; return ( <> + + isMyIssue ? mutateMyIssues() : isProfileIssue ? mutateProfileIssues() : mutateIssues() + } + projectId={myIssueProjectId ? myIssueProjectId : projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} + /> {groupedIssues && (
{Object.keys(groupedIssues).map((singleGroup) => { @@ -51,6 +83,7 @@ export const AllLists: React.FC = ({ currentState={currentState} addIssueToGroup={() => addIssueToGroup(singleGroup)} handleIssueAction={handleIssueAction} + handleMyIssueOpen={handleMyIssueOpen} openIssuesListModal={openIssuesListModal} removeIssue={removeIssue} disableUserActions={disableUserActions} diff --git a/web/components/core/views/list-view/single-issue.tsx b/web/components/core/views/list-view/single-issue.tsx index 1e5d551c3..ab5c080ca 100644 --- a/web/components/core/views/list-view/single-issue.tsx +++ b/web/components/core/views/list-view/single-issue.tsx @@ -61,6 +61,7 @@ type Props = { makeIssueCopy: () => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; + handleMyIssueOpen?: (issue: IIssue) => void; disableUserActions: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; @@ -76,6 +77,7 @@ export const SingleListIssue: React.FC = ({ removeIssue, groupTitle, handleDeleteIssue, + handleMyIssueOpen, disableUserActions, user, userAuth, @@ -178,6 +180,16 @@ export const SingleListIssue: React.FC = ({ ? `/${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}` : `/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`; + const openPeekOverview = (issue: IIssue) => { + const { query } = router; + + if (handleMyIssueOpen) handleMyIssueOpen(issue); + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue.id }, + }); + }; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues; @@ -220,23 +232,27 @@ export const SingleListIssue: React.FC = ({ }} >
void; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; openIssuesListModal?: (() => void) | null; + handleMyIssueOpen?: (issue: IIssue) => void; removeIssue: ((bridgeId: string, issueId: string) => void) | null; disableUserActions: boolean; disableAddIssueOption?: boolean; @@ -55,6 +56,7 @@ export const SingleList: React.FC = ({ addIssueToGroup, handleIssueAction, openIssuesListModal, + handleMyIssueOpen, removeIssue, disableUserActions, disableAddIssueOption = false, @@ -251,6 +253,7 @@ export const SingleList: React.FC = ({ editIssue={() => handleIssueAction(issue, "edit")} makeIssueCopy={() => handleIssueAction(issue, "copy")} handleDeleteIssue={() => handleIssueAction(issue, "delete")} + handleMyIssueOpen={handleMyIssueOpen} removeIssue={() => { if (removeIssue !== null && issue.bridge_id) removeIssue(issue.bridge_id, issue.id); diff --git a/web/components/cycles/gantt-chart/cycle-issues-layout.tsx b/web/components/cycles/gantt-chart/cycle-issues-layout.tsx index 1c78da096..b70b16f03 100644 --- a/web/components/cycles/gantt-chart/cycle-issues-layout.tsx +++ b/web/components/cycles/gantt-chart/cycle-issues-layout.tsx @@ -8,11 +8,15 @@ import { updateGanttIssue } from "components/gantt-chart/hooks/block-update"; import useProjectDetails from "hooks/use-project-details"; // components import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart"; -import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues"; +import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues"; // types import { IIssue } from "types"; -export const CycleIssuesGanttChartView = () => { +type Props = { + disableUserActions: boolean; +}; + +export const CycleIssuesGanttChartView: React.FC = ({ disableUserActions }) => { const router = useRouter(); const { workspaceSlug, projectId, cycleId } = router.query; @@ -30,23 +34,31 @@ export const CycleIssuesGanttChartView = () => { const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15; return ( -
- - updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) - } - SidebarBlockRender={IssueGanttSidebarBlock} - BlockRender={IssueGanttBlock} - enableBlockLeftResize={isAllowed} - enableBlockRightResize={isAllowed} - enableBlockMove={isAllowed} - enableReorder={displayFilters.order_by === "sort_order" && isAllowed} - bottomSpacing + <> + mutateGanttIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} /> -
+
+ + updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) + } + SidebarBlockRender={IssueGanttSidebarBlock} + BlockRender={IssueGanttBlock} + enableBlockLeftResize={isAllowed} + enableBlockRightResize={isAllowed} + enableBlockMove={isAllowed} + enableReorder={displayFilters.order_by === "sort_order" && isAllowed} + bottomSpacing + /> +
+ ); }; diff --git a/web/components/issues/form.tsx b/web/components/issues/form.tsx index 177155f10..ae8a01896 100644 --- a/web/components/issues/form.tsx +++ b/web/components/issues/form.tsx @@ -23,14 +23,7 @@ import { import { CreateStateModal } from "components/states"; import { CreateLabelModal } from "components/labels"; // ui -import { - CustomMenu, - Input, - Loader, - PrimaryButton, - SecondaryButton, - ToggleSwitch, -} from "components/ui"; +import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui"; import { TipTapEditor } from "components/tiptap"; // icons import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; diff --git a/web/components/issues/gantt-chart/blocks.tsx b/web/components/issues/gantt-chart/blocks.tsx index 0834e3e79..ef4919780 100644 --- a/web/components/issues/gantt-chart/blocks.tsx +++ b/web/components/issues/gantt-chart/blocks.tsx @@ -11,13 +11,21 @@ import { IIssue } from "types"; export const IssueGanttBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - const { workspaceSlug } = router.query; + + const openPeekOverview = () => { + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: data.id }, + }); + }; return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} + onClick={openPeekOverview} >
{ // rendering issues on gantt sidebar export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => { const router = useRouter(); - const { workspaceSlug } = router.query; const duration = findTotalDaysInRange(data?.start_date ?? "", data?.target_date ?? "", true); + const openPeekOverview = () => { + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: data.id }, + }); + }; + return (
router.push(`/${workspaceSlug}/projects/${data?.project}/issues/${data?.id}`)} + onClick={openPeekOverview} >
diff --git a/web/components/issues/gantt-chart/layout.tsx b/web/components/issues/gantt-chart/layout.tsx index a78319a4b..ed4cd3d70 100644 --- a/web/components/issues/gantt-chart/layout.tsx +++ b/web/components/issues/gantt-chart/layout.tsx @@ -8,11 +8,15 @@ import { updateGanttIssue } from "components/gantt-chart/hooks/block-update"; import useProjectDetails from "hooks/use-project-details"; // components import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart"; -import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues"; +import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues"; // types import { IIssue } from "types"; -export const IssueGanttChartView = () => { +type Props = { + disableUserActions: boolean; +}; + +export const IssueGanttChartView: React.FC = ({ disableUserActions }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -29,23 +33,31 @@ export const IssueGanttChartView = () => { const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15; return ( -
- - updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) - } - BlockRender={IssueGanttBlock} - SidebarBlockRender={IssueGanttSidebarBlock} - enableBlockLeftResize={isAllowed} - enableBlockRightResize={isAllowed} - enableBlockMove={isAllowed} - enableReorder={displayFilters.order_by === "sort_order" && isAllowed} - bottomSpacing + <> + mutateGanttIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} /> -
+
+ + updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) + } + BlockRender={IssueGanttBlock} + SidebarBlockRender={IssueGanttSidebarBlock} + enableBlockLeftResize={isAllowed} + enableBlockRightResize={isAllowed} + enableBlockMove={isAllowed} + enableReorder={displayFilters.order_by === "sort_order" && isAllowed} + bottomSpacing + /> +
+ ); }; diff --git a/web/components/issues/my-issues/my-issues-view.tsx b/web/components/issues/my-issues/my-issues-view.tsx index 81a456079..7dc5c8d20 100644 --- a/web/components/issues/my-issues/my-issues-view.tsx +++ b/web/components/issues/my-issues/my-issues-view.tsx @@ -57,7 +57,7 @@ export const MyIssuesView: React.FC = ({ const { user } = useUserAuth(); const { groupedIssues, mutateMyIssues, isEmpty, params } = useMyIssues(workspaceSlug?.toString()); - const { filters, setFilters, displayFilters, setDisplayFilters, properties } = useMyIssuesFilters( + const { filters, setFilters, displayFilters, properties } = useMyIssuesFilters( workspaceSlug?.toString() ); diff --git a/web/components/modules/gantt-chart/module-issues-layout.tsx b/web/components/modules/gantt-chart/module-issues-layout.tsx index ca8aae527..c7bef4b26 100644 --- a/web/components/modules/gantt-chart/module-issues-layout.tsx +++ b/web/components/modules/gantt-chart/module-issues-layout.tsx @@ -1,5 +1,3 @@ -import { FC } from "react"; - import { useRouter } from "next/router"; // hooks @@ -10,13 +8,13 @@ import { updateGanttIssue } from "components/gantt-chart/hooks/block-update"; import useProjectDetails from "hooks/use-project-details"; // components import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart"; -import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues"; +import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues"; // types import { IIssue } from "types"; -type Props = {}; +type Props = { disableUserActions: boolean }; -export const ModuleIssuesGanttChartView: FC = ({}) => { +export const ModuleIssuesGanttChartView: React.FC = ({ disableUserActions }) => { const router = useRouter(); const { workspaceSlug, projectId, moduleId } = router.query; @@ -34,23 +32,31 @@ export const ModuleIssuesGanttChartView: FC = ({}) => { const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15; return ( -
- - updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) - } - SidebarBlockRender={IssueGanttSidebarBlock} - BlockRender={IssueGanttBlock} - enableBlockLeftResize={isAllowed} - enableBlockRightResize={isAllowed} - enableBlockMove={isAllowed} - enableReorder={displayFilters.order_by === "sort_order" && isAllowed} - bottomSpacing + <> + mutateGanttIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} /> -
+
+ + updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) + } + SidebarBlockRender={IssueGanttSidebarBlock} + BlockRender={IssueGanttBlock} + enableBlockLeftResize={isAllowed} + enableBlockRightResize={isAllowed} + enableBlockMove={isAllowed} + enableReorder={displayFilters.order_by === "sort_order" && isAllowed} + bottomSpacing + /> +
+ ); }; diff --git a/web/components/views/gantt-chart.tsx b/web/components/views/gantt-chart.tsx index b25f034cd..6f43a32e6 100644 --- a/web/components/views/gantt-chart.tsx +++ b/web/components/views/gantt-chart.tsx @@ -1,5 +1,3 @@ -import { FC } from "react"; - import { useRouter } from "next/router"; // hooks @@ -9,13 +7,13 @@ import { updateGanttIssue } from "components/gantt-chart/hooks/block-update"; import useProjectDetails from "hooks/use-project-details"; // components import { GanttChartRoot, renderIssueBlocksStructure } from "components/gantt-chart"; -import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues"; +import { IssueGanttBlock, IssueGanttSidebarBlock, IssuePeekOverview } from "components/issues"; // types import { IIssue } from "types"; -type Props = {}; +type Props = { disableUserActions: boolean }; -export const ViewIssuesGanttChartView: FC = ({}) => { +export const ViewIssuesGanttChartView: React.FC = ({ disableUserActions }) => { const router = useRouter(); const { workspaceSlug, projectId, viewId } = router.query; @@ -31,22 +29,30 @@ export const ViewIssuesGanttChartView: FC = ({}) => { const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15; return ( -
- - updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) - } - SidebarBlockRender={IssueGanttSidebarBlock} - BlockRender={IssueGanttBlock} - enableBlockLeftResize={isAllowed} - enableBlockRightResize={isAllowed} - enableBlockMove={isAllowed} - enableReorder={isAllowed} + <> + mutateGanttIssues()} + projectId={projectId?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} + readOnly={disableUserActions} /> -
+
+ + updateGanttIssue(block, payload, mutateGanttIssues, user, workspaceSlug?.toString()) + } + SidebarBlockRender={IssueGanttSidebarBlock} + BlockRender={IssueGanttBlock} + enableBlockLeftResize={isAllowed} + enableBlockRightResize={isAllowed} + enableBlockMove={isAllowed} + enableReorder={isAllowed} + /> +
+ ); }; diff --git a/web/hooks/use-calendar-issues-view.tsx b/web/hooks/use-calendar-issues-view.tsx index 362cd741f..289aed2ac 100644 --- a/web/hooks/use-calendar-issues-view.tsx +++ b/web/hooks/use-calendar-issues-view.tsx @@ -44,7 +44,7 @@ const useCalendarIssuesView = () => { target_date: displayFilters?.calendar_date_range, }; - const { data: projectCalendarIssues } = useSWR( + const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params) : null, @@ -54,7 +54,7 @@ const useCalendarIssuesView = () => { : null ); - const { data: cycleCalendarIssues } = useSWR( + const { data: cycleCalendarIssues, mutate: mutateCycleCalendarIssues } = useSWR( workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : null, @@ -69,7 +69,7 @@ const useCalendarIssuesView = () => { : null ); - const { data: moduleCalendarIssues } = useSWR( + const { data: moduleCalendarIssues, mutate: mutateModuleCalendarIssues } = useSWR( workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : null, @@ -84,7 +84,7 @@ const useCalendarIssuesView = () => { : null ); - const { data: viewCalendarIssues } = useSWR( + const { data: viewCalendarIssues, mutate: mutateViewCalendarIssues } = useSWR( workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null, workspaceSlug && projectId && viewId && params ? () => @@ -104,6 +104,13 @@ const useCalendarIssuesView = () => { displayFilters, setDisplayFilters, calendarIssues: calendarIssues ?? [], + mutateIssues: cycleId + ? mutateCycleCalendarIssues + : moduleId + ? mutateModuleCalendarIssues + : viewId + ? mutateViewCalendarIssues + : mutateProjectCalendarIssues, filters, setFilters, params, From 9bac7cb03627a04d47ea2fbbc15bbfce9ae2b69f Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:41:11 +0530 Subject: [PATCH 2/3] feat: issue link to create relation between issues (#2171) * feat: issue linking * fix: search params to filter out selected issue * style: changed icons * fix: build error on web-view * fix: build error * fix: build error on web-view component --- web/components/icons/index.ts | 1 + web/components/icons/related-icon.tsx | 41 ++++ .../issues/sidebar-select/blocked.tsx | 64 ++++-- .../issues/sidebar-select/blocker.tsx | 71 +++++-- .../issues/sidebar-select/duplicate.tsx | 172 ++++++++++++++++ web/components/issues/sidebar-select/index.ts | 2 + .../issues/sidebar-select/relates-to.tsx | 172 ++++++++++++++++ web/components/issues/sidebar.tsx | 72 ++++++- .../web-view/issue-properties-detail.tsx | 192 +++++++++++++----- web/components/web-view/select-blocked.tsx | 2 +- web/components/web-view/select-blocker.tsx | 2 +- web/pages/[workspaceSlug]/editor.tsx | 4 +- .../projects/[projectId]/issues/[issueId].tsx | 4 +- .../projects/[projectId]/issues/[issueId].tsx | 4 +- web/services/issues.service.ts | 46 +++++ web/services/track-event.service.ts | 16 ++ web/types/issues.d.ts | 27 ++- web/types/projects.d.ts | 2 +- 18 files changed, 793 insertions(+), 101 deletions(-) create mode 100644 web/components/icons/related-icon.tsx create mode 100644 web/components/issues/sidebar-select/duplicate.tsx create mode 100644 web/components/issues/sidebar-select/relates-to.tsx diff --git a/web/components/icons/index.ts b/web/components/icons/index.ts index d3be7f2a8..ab661a092 100644 --- a/web/components/icons/index.ts +++ b/web/components/icons/index.ts @@ -83,3 +83,4 @@ export * from "./archive-icon"; export * from "./clock-icon"; export * from "./bell-icon"; export * from "./single-comment-icon"; +export * from "./related-icon"; diff --git a/web/components/icons/related-icon.tsx b/web/components/icons/related-icon.tsx new file mode 100644 index 000000000..3abb4b1c3 --- /dev/null +++ b/web/components/icons/related-icon.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const RelatedIcon: React.FC = ({ + width = "24", + height = "24", + color = "rgb(var(--color-text-200))", + className, +}) => ( + + + + + +); diff --git a/web/components/issues/sidebar-select/blocked.tsx b/web/components/issues/sidebar-select/blocked.tsx index 02cfd3b16..9554a83ba 100644 --- a/web/components/issues/sidebar-select/blocked.tsx +++ b/web/components/issues/sidebar-select/blocked.tsx @@ -1,11 +1,13 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; - // react-hook-form import { UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; +// services +import issuesService from "services/issues.service"; // components import { ExistingIssuesListModal } from "components/core"; // icons @@ -29,10 +31,11 @@ export const SidebarBlockedSelect: React.FC = ({ }) => { const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); + const { user } = useUser(); const { setToastAlert } = useToast(); const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; const handleClose = () => { setIsBlockedModalOpen(false); @@ -62,21 +65,39 @@ export const SidebarBlockedSelect: React.FC = ({ }, })); - const newBlocked = [...watch("blocked_issues"), ...selectedIssues]; + if (!user) return; + + issuesService + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + related_list: [ + ...selectedIssues.map((issue) => ({ + issue: issueId as string, + relation_type: "blocked_by" as const, + related_issue_detail: issue.blocked_issue_detail, + related_issue: issue.blocked_issue_detail.id, + })), + ], + }) + .then((response) => { + submitChanges({ + related_issues: [ + ...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"), + ...response, + ], + }); + }); - submitChanges({ - blocked_issues: newBlocked, - blocks_list: newBlocked.map((i) => i.blocked_issue_detail?.id ?? ""), - }); handleClose(); }; + const blockedByIssue = watch("related_issues")?.filter((i) => i.relation_type === "blocked_by"); + return ( <> setIsBlockedModalOpen(false)} - searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + searchParams={{ issue_relation: true, issue_id: issueId }} handleOnSubmit={onSubmit} workspaceLevelToggle /> @@ -87,33 +108,42 @@ export const SidebarBlockedSelect: React.FC = ({
- {watch("blocked_issues") && watch("blocked_issues").length > 0 - ? watch("blocked_issues").map((issue) => ( + {blockedByIssue && blockedByIssue.length > 0 + ? blockedByIssue.map((relation) => (
- {`${issue.blocked_issue_detail?.project_detail.identifier}-${issue.blocked_issue_detail?.sequence_id}`} + {`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`}
- {watch("blocker_issues") && watch("blocker_issues").length > 0 - ? watch("blocker_issues").map((issue) => ( + {blockerIssue && blockerIssue.length > 0 + ? blockerIssue.map((relation) => (
- {`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + {`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`} +
+ )) + : null} +
+ +
+
+ + ); +}; diff --git a/web/components/issues/sidebar-select/index.ts b/web/components/issues/sidebar-select/index.ts index 5035325fd..8b083841e 100644 --- a/web/components/issues/sidebar-select/index.ts +++ b/web/components/issues/sidebar-select/index.ts @@ -8,3 +8,5 @@ export * from "./module"; export * from "./parent"; export * from "./priority"; export * from "./state"; +export * from "./duplicate"; +export * from "./relates-to"; diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx new file mode 100644 index 000000000..fb878daee --- /dev/null +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -0,0 +1,172 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; +// react-hook-form +import { UseFormWatch } from "react-hook-form"; +// hooks +import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; +// icons +import { X } from "lucide-react"; +import { BlockerIcon, RelatedIcon } from "components/icons"; +// components +import { ExistingIssuesListModal } from "components/core"; +// services +import issuesService from "services/issues.service"; +// types +import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types"; + +type Props = { + issueId?: string; + submitChanges: (formData: Partial) => void; + watch: UseFormWatch; + disabled?: boolean; +}; + +export const SidebarRelatesSelect: React.FC = (props) => { + const { issueId, submitChanges, watch, disabled = false } = props; + + const [isRelatesToModalOpen, setIsRelatesToModalOpen] = useState(false); + + const { user } = useUser(); + const { setToastAlert } = useToast(); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const handleClose = () => { + setIsRelatesToModalOpen(false); + }; + + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please select at least one issue.", + }); + + return; + } + + const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({ + blocker_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + project_detail: { + id: i.project_id, + identifier: i.project__identifier, + name: i.project__name, + }, + }, + })); + + if (!user) return; + + issuesService + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + related_list: [ + ...selectedIssues.map((issue) => ({ + issue: issueId as string, + related_issue_detail: issue.blocker_issue_detail, + related_issue: issue.blocker_issue_detail.id, + relation_type: "relates_to" as const, + })), + ], + }) + .then((response) => { + submitChanges({ + related_issues: [...watch("related_issues"), ...(response ?? [])], + }); + }); + + handleClose(); + }; + + const relatedToIssueRelation = [ + ...(watch("related_issues")?.filter((i) => i.relation_type === "relates_to") ?? []), + ...(watch("issue_relations") ?? []) + ?.filter((i) => i.relation_type === "relates_to") + .map((i) => ({ + ...i, + related_issue_detail: i.issue_detail, + related_issue: i.issue_detail?.id, + })), + ]; + + return ( + <> + setIsRelatesToModalOpen(false)} + searchParams={{ issue_relation: true, issue_id: issueId }} + handleOnSubmit={onSubmit} + workspaceLevelToggle + /> +
+
+ +

Relates to

+
+
+
+ {relatedToIssueRelation && relatedToIssueRelation.length > 0 + ? relatedToIssueRelation.map((relation) => ( +
+ + + {`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`} + + +
+ )) + : null} +
+ +
+
+ + ); +}; diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index a33d17705..1f48f7307 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -30,6 +30,8 @@ import { SidebarStateSelect, SidebarEstimateSelect, SidebarLabelSelect, + SidebarDuplicateSelect, + SidebarRelatesSelect, } from "components/issues"; // ui import { CustomDatePicker, Icon } from "components/ui"; @@ -76,6 +78,8 @@ type Props = { | "delete" | "all" | "subscribe" + | "duplicate" + | "relates_to" )[]; uneditable?: boolean; }; @@ -464,7 +468,19 @@ export const IssueDetailsSidebar: React.FC = ({ {(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && ( { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} /> @@ -472,7 +488,59 @@ export const IssueDetailsSidebar: React.FC = ({ {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} + watch={watchIssue} + disabled={memberRole.isGuest || memberRole.isViewer || uneditable} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && ( + { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} + watch={watchIssue} + disabled={memberRole.isGuest || memberRole.isViewer || uneditable} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && ( + { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} /> diff --git a/web/components/web-view/issue-properties-detail.tsx b/web/components/web-view/issue-properties-detail.tsx index 2fe0356f5..089f8950f 100644 --- a/web/components/web-view/issue-properties-detail.tsx +++ b/web/components/web-view/issue-properties-detail.tsx @@ -4,9 +4,21 @@ import React, { useState } from "react"; // next import { useRouter } from "next/router"; +// swr +import { mutate } from "swr"; + // react hook forms import { Control, Controller, useWatch } from "react-hook-form"; +// services +import issuesService from "services/issues.service"; + +// hooks +import useUser from "hooks/use-user"; + +// fetch keys +import { ISSUE_DETAILS } from "constants/fetch-keys"; + // icons import { BlockedIcon, BlockerIcon } from "components/icons"; import { ChevronDown, PlayIcon, User, X, CalendarDays, LayoutGrid, Users } from "lucide-react"; @@ -26,6 +38,7 @@ import { EstimateSelect, ParentSelect, BlockerSelect, + BlockedSelect, } from "components/web-view"; // types @@ -39,15 +52,16 @@ type Props = { export const IssuePropertiesDetail: React.FC = (props) => { const { control, submitChanges } = props; - const blockerIssue = useWatch({ - control, - name: "blocker_issues", - }); + const blockerIssue = + useWatch({ + control, + name: "issue_relations", + })?.filter((i) => i.relation_type === "blocked_by") || []; const blockedIssue = useWatch({ control, - name: "blocked_issues", - }); + name: "related_issues", + })?.filter((i) => i.relation_type === "blocked_by"); const startDate = useWatch({ control, @@ -55,12 +69,28 @@ export const IssuePropertiesDetail: React.FC = (props) => { }); const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId, issueId } = router.query; + + const { user } = useUser(); const [isViewAllOpen, setIsViewAllOpen] = useState(false); const { isEstimateActive } = useEstimateOption(); + const handleMutation = (data: any) => { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }; + return (
@@ -188,51 +218,80 @@ export const IssuePropertiesDetail: React.FC = (props) => { Blocking
- ( - - submitChanges({ - blocker_issues: val, - blockers_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), - }) - } - /> - )} + { + if (!user || !workspaceSlug || !projectId || !issueId) return; + + issuesService + .createIssueRelation( + workspaceSlug as string, + projectId as string, + issueId as string, + user, + { + related_list: [ + ...val.map((issue: any) => ({ + issue: issue.blocker_issue_detail.id, + relation_type: "blocked_by" as const, + related_issue: issueId as string, + related_issue_detail: issue.blocker_issue_detail, + })), + ], + } + ) + .then((response) => { + handleMutation({ + issue_relations: [ + ...blockerIssue, + ...(response ?? []).map((i: any) => ({ + id: i.id, + relation_type: i.relation_type, + issue_detail: i.related_issue_detail, + issue: i.related_issue, + })), + ], + }); + }); + }} />
{blockerIssue && blockerIssue.map((issue) => (
- {`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + {`${issue.issue_detail?.project_detail.identifier}-${issue.issue_detail?.sequence_id}`}
- ( - - submitChanges({ - blocked_issues: val, - blocks_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), - }) - } - /> - )} + { + if (!user || !workspaceSlug || !projectId || !issueId) return; + + issuesService + .createIssueRelation( + workspaceSlug as string, + projectId as string, + issueId as string, + user, + { + related_list: [ + ...val.map((issue: any) => ({ + issue: issue.blocked_issue_detail.id, + relation_type: "blocked_by" as const, + related_issue: issueId as string, + related_issue_detail: issue.blocked_issue_detail, + })), + ], + } + ) + .then((response) => { + handleMutation({ + related_issues: [ + ...blockedIssue, + ...(response ?? []).map((i: any) => ({ + id: i.id, + relation_type: i.relation_type, + issue_detail: i.related_issue_detail, + issue: i.related_issue, + })), + ], + }); + }); + }} />
{blockedIssue && blockedIssue.map((issue) => (
- {`${issue?.blocked_issue_detail?.project_detail?.identifier}-${issue?.blocked_issue_detail?.sequence_id}`} + {`${issue?.related_issue_detail?.project_detail?.identifier}-${issue?.related_issue_detail?.sequence_id}`}