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
This commit is contained in:
Dakshesh Jain 2023-10-04 14:55:41 +05:30 committed by GitHub
parent d9bd07886f
commit cecdf890de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 268 additions and 184 deletions

View File

@ -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> = (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> = (props) => {
scrollToBottom();
};
const handleAddIssueToGroup = () => {
if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else onCreateClick();
};
return (
<div className={`flex-shrink-0 ${!isCollapsed ? "" : "flex h-full flex-col w-96"}`}>
<CreateUpdateDraftIssueModal
isOpen={isCreateDraftIssueModalOpen}
handleClose={() => 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,
}}
/>
<BoardHeader
addIssueToGroup={addIssueToGroup}
addIssueToGroup={handleAddIssueToGroup}
currentState={currentState}
groupTitle={groupTitle}
isCollapsed={isCollapsed}
@ -218,21 +237,22 @@ export const SingleBoard: React.FC<Props> = (props) => {
{displayFilters?.group_by !== "created_by" && (
<div>
{type === "issue"
? !disableAddIssueOption && (
? !disableAddIssueOption &&
!isDraftIssuesPage && (
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
addIssueToGroup();
} else onCreateClick();
if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else onCreateClick();
}}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
)
: !disableUserActions && (
: !disableUserActions &&
!isDraftIssuesPage && (
<CustomMenu
customButton={
<button
@ -246,7 +266,13 @@ export const SingleBoard: React.FC<Props> = (props) => {
position="left"
noBorder
>
<CustomMenu.MenuItem onClick={() => onCreateClick()}>
<CustomMenu.MenuItem
onClick={() => {
if (isDraftIssuesPage) setIsCreateDraftIssueModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else onCreateClick();
}}
>
Create new
</CustomMenu.MenuItem>
{openIssuesListModal && (

View File

@ -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> = (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,6 +210,18 @@ export const SingleList: React.FC<Props> = (props) => {
if (!groupedIssues) return null;
return (
<>
<CreateUpdateDraftIssueModal
isOpen={isDraftIssuesModalOpen}
handleClose={() => 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,
}}
/>
<Disclosure as="div" defaultOpen>
{({ open }) => (
<div>
@ -241,9 +255,9 @@ export const SingleList: React.FC<Props> = (props) => {
type="button"
className="p-1 text-custom-text-200 hover:bg-custom-background-80"
onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
addIssueToGroup();
} else setIsCreateIssueFormOpen(true);
if (isDraftIssuesPage) setIsDraftIssuesModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else setIsCreateIssueFormOpen(true);
}}
>
<PlusIcon className="h-4 w-4" />
@ -331,19 +345,21 @@ export const SingleList: React.FC<Props> = (props) => {
prePopulatedData={{
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
[displayFilters?.group_by!]: groupTitle,
[displayFilters?.group_by! === "labels"
? "labels_list"
: displayFilters?.group_by!]:
displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle,
}}
/>
{!disableAddIssueOption && !isCreateIssueFormOpen && (
// TODO: add border here
{!disableAddIssueOption && !isCreateIssueFormOpen && !isDraftIssuesPage && (
<div className="w-full bg-custom-background-100 px-6 py-3 border-b border-custom-border-100">
<button
type="button"
onClick={() => {
if (isDraftIssuesPage || isMyIssuesPage || isProfileIssuesPage) {
addIssueToGroup();
} else setIsCreateIssueFormOpen(true);
if (isDraftIssuesPage) setIsDraftIssuesModalOpen(true);
else if (isMyIssuesPage || isProfileIssuesPage) addIssueToGroup();
else setIsCreateIssueFormOpen(true);
}}
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md"
>
@ -357,5 +373,6 @@ export const SingleList: React.FC<Props> = (props) => {
</div>
)}
</Disclosure>
</>
);
};

View File

@ -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<Props> = ({
const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
const buttonRef = useRef<HTMLButtonElement>(null);
const emojiPickerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -49,10 +51,12 @@ const EmojiIconPicker: React.FC<Props> = ({
}, [value, onChange]);
useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false));
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), buttonRef, emojiPickerRef);
return (
<Popover className="relative z-[1]">
<Popover.Button
ref={buttonRef}
onClick={() => setIsOpen((prev) => !prev)}
className="outline-none"
disabled={disabled}
@ -61,6 +65,8 @@ const EmojiIconPicker: React.FC<Props> = ({
</Popover.Button>
<Transition
show={isOpen}
static
as={React.Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
@ -68,11 +74,11 @@ const EmojiIconPicker: React.FC<Props> = ({
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Popover.Panel className="absolute z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg">
<div
<Popover.Panel
ref={emojiPickerRef}
className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl"
className="fixed z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg"
>
<div className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl">
<Tab.Group as="div" className="flex h-full w-full flex-col">
<Tab.List className="flex-0 -mx-2 flex justify-around gap-1 p-1">
{tabOptions.map((tab) => (

View File

@ -80,6 +80,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
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<ChartViewRootProps> = ({
SidebarBlockRender={SidebarBlockRender}
enableReorder={enableReorder}
/>
{chartBlocks && (
{chartBlocks && !(isCyclePage || isModulePage) && (
<div className="pl-2.5 py-3">
<GanttInlineCreateIssueForm
isOpen={isCreateIssueFormOpen}

View File

@ -8,6 +8,7 @@ import { Controller, useForm } from "react-hook-form";
import aiService from "services/ai.service";
// hooks
import useToast from "hooks/use-toast";
import useLocalStorage from "hooks/use-local-storage";
// components
import { GptAssistantModal } from "components/core";
import { ParentIssuesListModal } from "components/issues";
@ -60,6 +61,7 @@ interface IssueFormProps {
action?: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue"
) => Promise<void>;
data?: Partial<IIssue> | null;
isOpen: boolean;
prePopulatedData?: Partial<IIssue> | null;
projectId: string;
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
@ -89,6 +91,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const {
handleFormSubmit,
data,
isOpen,
prePopulatedData,
projectId,
setActiveProject,
@ -109,6 +112,8 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const [gptAssistantModal, setGptAssistantModal] = useState(false);
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {});
const editorRef = useRef<any>(null);
const router = useRouter();
@ -133,6 +138,33 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
const issueName = watch("name");
const payload: Partial<IIssue> = {
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<IssueFormProps> = (props) => {
)}
<form
onSubmit={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, "convertToNewIssue")
handleCreateUpdateIssue(formData, data ? "convertToNewIssue" : "createDraft")
)}
>
<div className="space-y-5">

View File

@ -385,6 +385,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<DraftIssueForm
isOpen={isOpen}
handleFormSubmit={handleFormSubmit}
prePopulatedData={prePopulateData}
data={data}

View File

@ -129,18 +129,19 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
const issueName = watch("name");
const payload: Partial<IIssue> = {
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(() => {

View File

@ -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<Props> = ({
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<Props> = ({
})
.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 (
<>
<ExistingIssuesListModal

View File

@ -15,7 +15,7 @@ import issuesService from "services/issues.service";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { BlockerIcon } from "components/icons";
// types
import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types";
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
type Props = {
issueId?: string;

View File

@ -75,10 +75,8 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
})),
],
})
.then((response) => {
submitChanges({
related_issues: [...watch("related_issues"), ...(response ?? [])],
});
.then(() => {
submitChanges();
});
handleClose();

View File

@ -75,10 +75,8 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
})),
],
})
.then((response) => {
submitChanges({
related_issues: [...watch("related_issues"), ...(response ?? [])],
});
.then(() => {
submitChanges();
});
handleClose();

View File

@ -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<Props> = ({
},
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<Props> = ({
},
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<Props> = ({
...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<Props> = ({
...data,
};
});
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}}
watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}

View File

@ -375,7 +375,7 @@ const Profile: NextPage = () => {
<div className="flex items-center justify-between py-2">
<PrimaryButton type="submit" loading={isSubmitting}>
{isSubmitting ? "Updating Project..." : "Update Project"}
{isSubmitting ? "Updating Profile..." : "Update Profile"}
</PrimaryButton>
</div>
</div>