From cecdf890de06ec04b23c0aaff1e3b18df59a729a Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:55:41 +0530 Subject: [PATCH] fix: issue relation mutation and draft issue (#2340) * fix: issue relation mutation and draft issue * fix: 'New Issue' in gantt view fix: emoji select going under * fix: profile page typo --- .../core/views/board-view/single-board.tsx | 40 ++- .../core/views/list-view/single-list.tsx | 297 +++++++++--------- web/components/emoji-icon-picker/index.tsx | 16 +- web/components/gantt-chart/chart/index.tsx | 5 +- web/components/issues/draft-issue-form.tsx | 34 +- web/components/issues/draft-issue-modal.tsx | 1 + web/components/issues/form.tsx | 25 +- .../issues/sidebar-select/blocked.tsx | 12 +- .../issues/sidebar-select/blocker.tsx | 2 +- .../issues/sidebar-select/duplicate.tsx | 6 +- .../issues/sidebar-select/relates-to.tsx | 6 +- web/components/issues/sidebar.tsx | 6 +- .../[workspaceSlug]/me/profile/index.tsx | 2 +- 13 files changed, 268 insertions(+), 184 deletions(-) diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index bdbfc27c29d..6d583e772b3 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/router"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import { Draggable } from "react-beautiful-dnd"; // components +import { CreateUpdateDraftIssueModal } from "components/issues"; import { BoardHeader, SingleBoardIssue, BoardInlineCreateIssueForm } from "components/core"; // ui import { CustomMenu } from "components/ui"; @@ -57,6 +58,7 @@ export const SingleBoard: React.FC = (props) => { const [isCollapsed, setIsCollapsed] = useState(true); const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); + const [isCreateDraftIssueModalOpen, setIsCreateDraftIssueModalOpen] = useState(false); const { displayFilters, groupedIssues } = viewProps; @@ -96,10 +98,27 @@ export const SingleBoard: React.FC = (props) => { scrollToBottom(); }; + const handleAddIssueToGroup = () => { + if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true); + else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup(); + else onCreateClick(); + }; + return (
+ setIsCreateDraftIssueModalOpen(false)} + prePopulateData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + [displayFilters?.group_by! === "labels" ? "labels_list" : displayFilters?.group_by!]: + displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle, + }} + /> + = (props) => { {displayFilters?.group_by !== "created_by" && (
{type === "issue" - ? !disableAddIssueOption && ( + ? !disableAddIssueOption && + !isDraftIssuesPage && ( ) - : !disableUserActions && ( + : !disableUserActions && + !isDraftIssuesPage && ( = (props) => { position="left" noBorder > - onCreateClick()}> + { + if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true); + else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup(); + else onCreateClick(); + }} + > Create new {openIssuesListModal && ( diff --git a/web/components/core/views/list-view/single-list.tsx b/web/components/core/views/list-view/single-list.tsx index 9d3e9cb96af..8cba6711221 100644 --- a/web/components/core/views/list-view/single-list.tsx +++ b/web/components/core/views/list-view/single-list.tsx @@ -13,6 +13,7 @@ import projectService from "services/project.service"; // hooks import useProjects from "hooks/use-projects"; // components +import { CreateUpdateDraftIssueModal } from "components/issues"; import { SingleListIssue, ListInlineCreateIssueForm } from "components/core"; // ui import { Avatar, CustomMenu } from "components/ui"; @@ -75,6 +76,7 @@ export const SingleList: React.FC = (props) => { const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false); + const [isDraftIssuesModalOpen, setIsDraftIssuesModalOpen] = useState(false); const isMyIssuesPage = router.pathname.split("/")[3] === "my-issues"; const isProfileIssuesPage = router.pathname.split("/")[2] === "profile"; @@ -208,154 +210,169 @@ export const SingleList: React.FC = (props) => { if (!groupedIssues) return null; return ( - - {({ open }) => ( -
-
- -
- {displayFilters?.group_by !== null && ( -
{getGroupIcon()}
- )} - {displayFilters?.group_by !== null ? ( -

- {getGroupTitle()} -

- ) : ( -

All Issues

- )} - - {groupedIssues[groupTitle as keyof IIssue].length} - -
-
- {isArchivedIssues ? ( - "" - ) : type === "issue" ? ( - !disableAddIssueOption && ( - - ) - ) : disableUserActions ? ( - "" - ) : ( - - -
- } - position="right" - noBorder - > - setIsCreateIssueFormOpen(true)}> - Create new - - {openIssuesListModal && ( - - Add an existing issue - - )} - - )} -
- - - {groupedIssues[groupTitle] ? ( - groupedIssues[groupTitle].length > 0 ? ( - groupedIssues[groupTitle].map((issue, index) => ( - handleIssueAction(issue, "edit")} - makeIssueCopy={() => handleIssueAction(issue, "copy")} - handleDeleteIssue={() => handleIssueAction(issue, "delete")} - handleDraftIssueSelect={ - handleDraftIssueAction - ? () => handleDraftIssueAction(issue, "edit") - : undefined - } - handleDraftIssueDelete={ - handleDraftIssueAction - ? () => handleDraftIssueAction(issue, "delete") - : undefined - } - handleMyIssueOpen={handleMyIssueOpen} - removeIssue={() => { - if (removeIssue !== null && issue.bridge_id) - removeIssue(issue.bridge_id, issue.id); - }} - disableUserActions={disableUserActions} - user={user} - userAuth={userAuth} - viewProps={viewProps} - /> - )) - ) : ( -

- No issues. -

- ) - ) : ( -
Loading...
- )} + <> + setIsDraftIssuesModalOpen(false)} + prePopulateData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + [displayFilters?.group_by! === "labels" ? "labels_list" : displayFilters?.group_by!]: + displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle, + }} + /> - setIsCreateIssueFormOpen(false)} - prePopulatedData={{ - ...(cycleId && { cycle: cycleId.toString() }), - ...(moduleId && { module: moduleId.toString() }), - [displayFilters?.group_by!]: groupTitle, - }} - /> - - {!disableAddIssueOption && !isCreateIssueFormOpen && ( - // TODO: add border here -
+ + {({ open }) => ( +
+
+ +
+ {displayFilters?.group_by !== null && ( +
{getGroupIcon()}
+ )} + {displayFilters?.group_by !== null ? ( +

+ {getGroupTitle()} +

+ ) : ( +

All Issues

+ )} + + {groupedIssues[groupTitle as keyof IIssue].length} + +
+
+ {isArchivedIssues ? ( + "" + ) : type === "issue" ? ( + !disableAddIssueOption && ( -
+ ) + ) : disableUserActions ? ( + "" + ) : ( + + +
+ } + position="right" + noBorder + > + setIsCreateIssueFormOpen(true)}> + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + )} - - -
- )} -
+
+ + + {groupedIssues[groupTitle] ? ( + groupedIssues[groupTitle].length > 0 ? ( + groupedIssues[groupTitle].map((issue, index) => ( + handleIssueAction(issue, "edit")} + makeIssueCopy={() => handleIssueAction(issue, "copy")} + handleDeleteIssue={() => handleIssueAction(issue, "delete")} + handleDraftIssueSelect={ + handleDraftIssueAction + ? () => handleDraftIssueAction(issue, "edit") + : undefined + } + handleDraftIssueDelete={ + handleDraftIssueAction + ? () => handleDraftIssueAction(issue, "delete") + : undefined + } + handleMyIssueOpen={handleMyIssueOpen} + removeIssue={() => { + if (removeIssue !== null && issue.bridge_id) + removeIssue(issue.bridge_id, issue.id); + }} + disableUserActions={disableUserActions} + user={user} + userAuth={userAuth} + viewProps={viewProps} + /> + )) + ) : ( +

+ No issues. +

+ ) + ) : ( +
Loading...
+ )} + + setIsCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + [displayFilters?.group_by! === "labels" + ? "labels_list" + : displayFilters?.group_by!]: + displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle, + }} + /> + + {!disableAddIssueOption && !isCreateIssueFormOpen && !isDraftIssuesPage && ( +
+ +
+ )} +
+
+
+ )} + + ); }; diff --git a/web/components/emoji-icon-picker/index.tsx b/web/components/emoji-icon-picker/index.tsx index ab4eb022e63..cc42dbec835 100644 --- a/web/components/emoji-icon-picker/index.tsx +++ b/web/components/emoji-icon-picker/index.tsx @@ -4,6 +4,7 @@ import { Tab, Transition, Popover } from "@headlessui/react"; // react colors import { TwitterPicker } from "react-color"; // hooks +import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // types import { Props } from "./types"; @@ -38,6 +39,7 @@ const EmojiIconPicker: React.FC = ({ const [recentEmojis, setRecentEmojis] = useState([]); + const buttonRef = useRef(null); const emojiPickerRef = useRef(null); useEffect(() => { @@ -49,10 +51,12 @@ const EmojiIconPicker: React.FC = ({ }, [value, onChange]); useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false)); + useDynamicDropdownPosition(isOpen, () => setIsOpen(false), buttonRef, emojiPickerRef); return ( setIsOpen((prev) => !prev)} className="outline-none" disabled={disabled} @@ -61,6 +65,8 @@ const EmojiIconPicker: React.FC = ({ = ({ leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - -
+ +
{tabOptions.map((tab) => ( diff --git a/web/components/gantt-chart/chart/index.tsx b/web/components/gantt-chart/chart/index.tsx index 61e3078d619..e21e803de1f 100644 --- a/web/components/gantt-chart/chart/index.tsx +++ b/web/components/gantt-chart/chart/index.tsx @@ -80,6 +80,9 @@ export const ChartViewRoot: FC = ({ const router = useRouter(); const { cycleId, moduleId } = router.query; + const isCyclePage = router.pathname.split("/")[4] === "cycles" && !cycleId; + const isModulePage = router.pathname.split("/")[4] === "modules" && !moduleId; + const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) => blocks && blocks.length > 0 ? blocks.map((block: any) => ({ @@ -317,7 +320,7 @@ export const ChartViewRoot: FC = ({ SidebarBlockRender={SidebarBlockRender} enableReorder={enableReorder} /> - {chartBlocks && ( + {chartBlocks && !(isCyclePage || isModulePage) && (
Promise; data?: Partial | null; + isOpen: boolean; prePopulatedData?: Partial | null; projectId: string; setActiveProject: React.Dispatch>; @@ -89,6 +91,7 @@ export const DraftIssueForm: FC = (props) => { const { handleFormSubmit, data, + isOpen, prePopulatedData, projectId, setActiveProject, @@ -109,6 +112,8 @@ export const DraftIssueForm: FC = (props) => { const [gptAssistantModal, setGptAssistantModal] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); + const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {}); + const editorRef = useRef(null); const router = useRouter(); @@ -133,6 +138,33 @@ export const DraftIssueForm: FC = (props) => { const issueName = watch("name"); + const payload: Partial = { + name: watch("name"), + description: watch("description"), + description_html: watch("description_html"), + state: watch("state"), + priority: watch("priority"), + assignees: watch("assignees"), + labels: watch("labels"), + start_date: watch("start_date"), + target_date: watch("target_date"), + project: watch("project"), + parent: watch("parent"), + cycle: watch("cycle"), + module: watch("module"), + }; + + useEffect(() => { + if (!isOpen || data) return; + + setLocalStorageValue( + JSON.stringify({ + ...payload, + }) + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(payload), isOpen, data]); + const onClose = () => { handleClose(); }; @@ -273,7 +305,7 @@ export const DraftIssueForm: FC = (props) => { )}
- handleCreateUpdateIssue(formData, "convertToNewIssue") + handleCreateUpdateIssue(formData, data ? "convertToNewIssue" : "createDraft") )} >
diff --git a/web/components/issues/draft-issue-modal.tsx b/web/components/issues/draft-issue-modal.tsx index b6479d067f7..ca37ae03f1f 100644 --- a/web/components/issues/draft-issue-modal.tsx +++ b/web/components/issues/draft-issue-modal.tsx @@ -385,6 +385,7 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = > = (props) => { const issueName = watch("name"); const payload: Partial = { - name: getValues("name"), - description: getValues("description"), - state: getValues("state"), - priority: getValues("priority"), - assignees: getValues("assignees"), - labels: getValues("labels"), - start_date: getValues("start_date"), - target_date: getValues("target_date"), - project: getValues("project"), - parent: getValues("parent"), - cycle: getValues("cycle"), - module: getValues("module"), + name: watch("name"), + description: watch("description"), + description_html: watch("description_html"), + state: watch("state"), + priority: watch("priority"), + assignees: watch("assignees"), + labels: watch("labels"), + start_date: watch("start_date"), + target_date: watch("target_date"), + project: watch("project"), + parent: watch("parent"), + cycle: watch("cycle"), + module: watch("module"), }; useEffect(() => { diff --git a/web/components/issues/sidebar-select/blocked.tsx b/web/components/issues/sidebar-select/blocked.tsx index d7e448377c8..a4ebe010881 100644 --- a/web/components/issues/sidebar-select/blocked.tsx +++ b/web/components/issues/sidebar-select/blocked.tsx @@ -14,7 +14,7 @@ import { ExistingIssuesListModal } from "components/core"; import { XMarkIcon } from "@heroicons/react/24/outline"; import { BlockedIcon } from "components/icons"; // types -import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; +import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types"; type Props = { issueId?: string; @@ -41,6 +41,9 @@ export const SidebarBlockedSelect: React.FC = ({ setIsBlockedModalOpen(false); }; + const blockedByIssue = + watch("related_issues")?.filter((i) => i.relation_type === "blocked_by") || []; + const onSubmit = async (data: ISearchIssueResponse[]) => { if (data.length === 0) { setToastAlert({ @@ -80,18 +83,13 @@ export const SidebarBlockedSelect: React.FC = ({ }) .then((response) => { submitChanges({ - related_issues: [ - ...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"), - ...response, - ], + related_issues: [...watch("related_issues"), ...response], }); }); handleClose(); }; - const blockedByIssue = watch("related_issues")?.filter((i) => i.relation_type === "blocked_by"); - return ( <> = (props) => { })), ], }) - .then((response) => { - submitChanges({ - related_issues: [...watch("related_issues"), ...(response ?? [])], - }); + .then(() => { + submitChanges(); }); handleClose(); diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx index deadf4d2041..b9e56dbeb51 100644 --- a/web/components/issues/sidebar-select/relates-to.tsx +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -75,10 +75,8 @@ export const SidebarRelatesSelect: React.FC = (props) => { })), ], }) - .then((response) => { - submitChanges({ - related_issues: [...watch("related_issues"), ...(response ?? [])], - }); + .then(() => { + submitChanges(); }); handleClose(); diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index 5455192fbe8..e0c370eafaa 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -53,7 +53,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; // types import type { ICycle, IIssue, IIssueLink, linkDetails, IModule } from "types"; // fetch-keys -import { ISSUE_DETAILS } from "constants/fetch-keys"; +import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; import { ContrastIcon } from "components/icons"; type Props = { @@ -480,6 +480,7 @@ export const IssueDetailsSidebar: React.FC = ({ }, false ); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} @@ -500,6 +501,7 @@ export const IssueDetailsSidebar: React.FC = ({ }, false ); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} @@ -517,6 +519,7 @@ export const IssueDetailsSidebar: React.FC = ({ ...data, }; }); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} @@ -534,6 +537,7 @@ export const IssueDetailsSidebar: React.FC = ({ ...data, }; }); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} diff --git a/web/pages/[workspaceSlug]/me/profile/index.tsx b/web/pages/[workspaceSlug]/me/profile/index.tsx index 40ba8ecf3aa..8738ff46f15 100644 --- a/web/pages/[workspaceSlug]/me/profile/index.tsx +++ b/web/pages/[workspaceSlug]/me/profile/index.tsx @@ -375,7 +375,7 @@ const Profile: NextPage = () => {
- {isSubmitting ? "Updating Project..." : "Update Project"} + {isSubmitting ? "Updating Profile..." : "Update Profile"}