mirror of
https://github.com/makeplane/plane.git
synced 2026-02-05 05:28:56 -06:00
[WEB-3712] improvement: create draft work item logic (#6847)
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { Check, ChevronDown, Info, Search } from "lucide-react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
// plane helpers
|
||||
import { usePopper } from "react-popper";
|
||||
// plane imports
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// hooks
|
||||
import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";
|
||||
// helpers
|
||||
// local imports
|
||||
import { cn } from "../../helpers";
|
||||
// types
|
||||
import { ICustomSearchSelectProps } from "./helper";
|
||||
// local components
|
||||
import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { ICustomSearchSelectProps } from "./helper";
|
||||
|
||||
export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
|
||||
const {
|
||||
@@ -36,6 +33,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
|
||||
optionsClassName = "",
|
||||
value,
|
||||
tabIndex,
|
||||
noResultsMessage = "No matches found",
|
||||
} = props;
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
@@ -201,7 +199,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : (
|
||||
<p className="text-custom-text-400 italic py-1 px-1.5">No matches found</p>
|
||||
<p className="text-custom-text-400 italic py-1 px-1.5">{noResultsMessage}</p>
|
||||
)
|
||||
) : (
|
||||
<p className="text-custom-text-400 italic py-1 px-1.5">Loading...</p>
|
||||
|
||||
@@ -43,6 +43,7 @@ interface CustomSearchSelectProps {
|
||||
footerOption?: JSX.Element;
|
||||
onChange: any;
|
||||
onClose?: () => void;
|
||||
noResultsMessage?: string;
|
||||
options:
|
||||
| {
|
||||
value: any;
|
||||
|
||||
@@ -8,6 +8,7 @@ export type TWorkItemTemplateSelect = {
|
||||
placeholder?: string;
|
||||
renderChevron?: boolean;
|
||||
dropDownContainerClassName?: string;
|
||||
handleModalClose: () => void;
|
||||
handleFormChange?: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,27 +38,34 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
const { createIssue } = useWorkspaceDraftIssues();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const sanitizeChanges = (): Partial<TIssue> => {
|
||||
const sanitizedChanges = { ...changesMade };
|
||||
Object.entries(sanitizedChanges).forEach(([key, value]) => {
|
||||
const issueKey = key as keyof TIssue;
|
||||
if (value === null || value === undefined || value === "") delete sanitizedChanges[issueKey];
|
||||
if (typeof value === "object" && isEmpty(value)) delete sanitizedChanges[issueKey];
|
||||
if (Array.isArray(value) && value.length === 0) delete sanitizedChanges[issueKey];
|
||||
if (issueKey === "project_id") delete sanitizedChanges.project_id;
|
||||
if (issueKey === "priority" && value && value === "none") delete sanitizedChanges.priority;
|
||||
if (
|
||||
issueKey === "description_html" &&
|
||||
changesMade?.description_html &&
|
||||
isEmptyHtmlString(changesMade.description_html, ["img"])
|
||||
)
|
||||
delete sanitizedChanges.description_html;
|
||||
});
|
||||
return sanitizedChanges;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
// If the user is updating an existing work item, we don't need to show the discard modal
|
||||
if (data?.id) {
|
||||
onClose();
|
||||
setIssueDiscardModal(false);
|
||||
} else {
|
||||
if (changesMade) {
|
||||
Object.entries(changesMade).forEach(([key, value]) => {
|
||||
const issueKey = key as keyof TIssue;
|
||||
if (value === null || value === undefined || value === "") delete changesMade[issueKey];
|
||||
if (typeof value === "object" && isEmpty(value)) delete changesMade[issueKey];
|
||||
if (Array.isArray(value) && value.length === 0) delete changesMade[issueKey];
|
||||
if (issueKey === "project_id") delete changesMade.project_id;
|
||||
if (issueKey === "priority" && value && value === "none") delete changesMade.priority;
|
||||
if (
|
||||
issueKey === "description_html" &&
|
||||
changesMade.description_html &&
|
||||
isEmptyHtmlString(changesMade.description_html, ["img"])
|
||||
)
|
||||
delete changesMade.description_html;
|
||||
});
|
||||
if (isEmpty(changesMade)) {
|
||||
const sanitizedChanges = sanitizeChanges();
|
||||
if (isEmpty(sanitizedChanges)) {
|
||||
onClose();
|
||||
setIssueDiscardModal(false);
|
||||
} else setIssueDiscardModal(true);
|
||||
@@ -119,6 +126,14 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDraftAndClose = () => {
|
||||
const sanitizedChanges = sanitizeChanges();
|
||||
if (!data?.id && !isEmpty(sanitizedChanges)) {
|
||||
handleCreateDraftIssue();
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmIssueDiscard
|
||||
@@ -131,7 +146,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
<IssueFormRoot {...props} onClose={handleClose} />
|
||||
<IssueFormRoot {...props} onClose={handleClose} handleDraftAndClose={handleDraftAndClose} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface IssueFormProps {
|
||||
};
|
||||
isDuplicateModalOpen: boolean;
|
||||
handleDuplicateIssueModal: (isOpen: boolean) => void;
|
||||
handleDraftAndClose?: () => void;
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
storeType: EIssuesStoreType;
|
||||
}
|
||||
@@ -86,6 +87,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
},
|
||||
isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal,
|
||||
handleDraftAndClose,
|
||||
isProjectSelectionDisabled = false,
|
||||
storeType,
|
||||
} = props;
|
||||
@@ -235,14 +237,22 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
await onSubmit(submitData, is_draft_issue)
|
||||
.then(() => {
|
||||
setGptAssistantModal(false);
|
||||
reset({
|
||||
...DEFAULT_WORK_ITEM_FORM_VALUES,
|
||||
...(isCreateMoreToggleEnabled ? { ...data } : {}),
|
||||
project_id: getValues<"project_id">("project_id"),
|
||||
type_id: getValues<"type_id">("type_id"),
|
||||
description_html: data?.description_html ?? "<p></p>",
|
||||
});
|
||||
editorRef?.current?.clearEditor();
|
||||
if (isCreateMoreToggleEnabled && workItemTemplateId) {
|
||||
handleTemplateChange({
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
reset,
|
||||
editorRef,
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
...DEFAULT_WORK_ITEM_FORM_VALUES,
|
||||
...(isCreateMoreToggleEnabled ? { ...data } : {}),
|
||||
project_id: getValues<"project_id">("project_id"),
|
||||
type_id: getValues<"type_id">("type_id"),
|
||||
description_html: data?.description_html ?? "<p></p>",
|
||||
});
|
||||
editorRef?.current?.clearEditor();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
@@ -389,6 +399,13 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
<WorkItemTemplateSelect
|
||||
projectId={projectId}
|
||||
typeId={watch("type_id")}
|
||||
handleModalClose={() => {
|
||||
if (handleDraftAndClose) {
|
||||
handleDraftAndClose();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
handleFormChange={handleFormChange}
|
||||
renderChevron
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user