[WEB-2577] improvement: use common create/update issue modal for accepting intake issues for consistency (#5830)
* [WEB-2577] improvement: use common create/update issue modal for accepting intake issues for consistency * fix: lint errors. * chore: minor UX copy fix. * chore: minor indentation fix.
This commit is contained in:
parent
fa25a816a7
commit
662b99da92
18 changed files with 95 additions and 264 deletions
|
|
@ -8,7 +8,7 @@ import { RefreshCcw } from "lucide-react";
|
||||||
import { Breadcrumbs, Button, Intake, Header } from "@plane/ui";
|
import { Breadcrumbs, Button, Intake, Header } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink, Logo } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
import { InboxIssueCreateModalRoot } from "@/components/inbox";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store";
|
import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||||
|
|
@ -69,12 +69,11 @@ export const ProjectInboxHeader: FC = observer(() => {
|
||||||
<Header.RightItem>
|
<Header.RightItem>
|
||||||
{currentProjectDetails?.inbox_view && workspaceSlug && projectId && isAuthorized ? (
|
{currentProjectDetails?.inbox_view && workspaceSlug && projectId && isAuthorized ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<InboxIssueCreateEditModalRoot
|
<InboxIssueCreateModalRoot
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId.toString()}
|
projectId={projectId.toString()}
|
||||||
modalState={createIssueModal}
|
modalState={createIssueModal}
|
||||||
handleModalClose={() => setCreateIssueModal(false)}
|
handleModalClose={() => setCreateIssueModal(false)}
|
||||||
issue={undefined}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button variant="primary" size="sm" onClick={() => setCreateIssueModal(true)}>
|
<Button variant="primary" size="sm" onClick={() => setCreateIssueModal(true)}>
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,11 @@ import {
|
||||||
DeclineIssueModal,
|
DeclineIssueModal,
|
||||||
DeleteInboxIssueModal,
|
DeleteInboxIssueModal,
|
||||||
InboxIssueActionsMobileHeader,
|
InboxIssueActionsMobileHeader,
|
||||||
InboxIssueCreateEditModalRoot,
|
|
||||||
InboxIssueSnoozeModal,
|
InboxIssueSnoozeModal,
|
||||||
InboxIssueStatus,
|
InboxIssueStatus,
|
||||||
SelectDuplicateInboxIssueModal,
|
SelectDuplicateInboxIssueModal,
|
||||||
} from "@/components/inbox";
|
} from "@/components/inbox";
|
||||||
import { IssueUpdateStatus } from "@/components/issues";
|
import { CreateUpdateIssueModal, IssueUpdateStatus } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
import { findHowManyDaysLeft } from "@/helpers/date-time.helper";
|
import { findHowManyDaysLeft } from "@/helpers/date-time.helper";
|
||||||
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
||||||
|
|
@ -70,6 +69,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
||||||
const { currentTab, deleteInboxIssue, filteredInboxIssueIds } = useProjectInbox();
|
const { currentTab, deleteInboxIssue, filteredInboxIssueIds } = useProjectInbox();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
const { currentProjectDetails } = useProject();
|
||||||
|
|
||||||
const router = useAppRouter();
|
const router = useAppRouter();
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
|
|
@ -234,16 +234,19 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
||||||
value={inboxIssue?.duplicate_to}
|
value={inboxIssue?.duplicate_to}
|
||||||
onSubmit={handleInboxIssueDuplicate}
|
onSubmit={handleInboxIssueDuplicate}
|
||||||
/>
|
/>
|
||||||
|
<CreateUpdateIssueModal
|
||||||
<InboxIssueCreateEditModalRoot
|
data={inboxIssue?.issue}
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
isOpen={acceptIssueModal}
|
||||||
projectId={projectId.toString()}
|
onClose={() => setAcceptIssueModal(false)}
|
||||||
modalState={acceptIssueModal}
|
beforeFormSubmit={handleInboxIssueAccept}
|
||||||
handleModalClose={() => setAcceptIssueModal(false)}
|
withDraftIssueWrapper={false}
|
||||||
issue={inboxIssue?.issue}
|
fetchIssueDetails={false}
|
||||||
onSubmit={handleInboxIssueAccept}
|
modalTitle={`Move ${currentProjectDetails?.identifier}-${issue?.sequence_id} to project issues`}
|
||||||
|
primaryButtonText={{
|
||||||
|
default: "Add to project",
|
||||||
|
loading: "Adding",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeclineIssueModal
|
<DeclineIssueModal
|
||||||
data={inboxIssue?.issue || {}}
|
data={inboxIssue?.issue || {}}
|
||||||
isOpen={declineIssueModal}
|
isOpen={declineIssueModal}
|
||||||
|
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// editor
|
|
||||||
import { EditorRefApi } from "@plane/editor";
|
|
||||||
// types
|
|
||||||
import { TIssue } from "@plane/types";
|
|
||||||
// ui
|
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
|
||||||
// components
|
|
||||||
import {
|
|
||||||
InboxIssueTitle,
|
|
||||||
InboxIssueDescription,
|
|
||||||
InboxIssueProperties,
|
|
||||||
} from "@/components/inbox/modals/create-edit-modal";
|
|
||||||
// constants
|
|
||||||
import { ISSUE_UPDATED } from "@/constants/event-tracker";
|
|
||||||
// helpers
|
|
||||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
|
||||||
// hooks
|
|
||||||
import { useEventTracker, useInboxIssues, useProject, useWorkspace } from "@/hooks/store";
|
|
||||||
|
|
||||||
type TInboxIssueEditRoot = {
|
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
|
||||||
issue: Partial<TIssue>;
|
|
||||||
handleModalClose: () => void;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
|
|
||||||
const { workspaceSlug, projectId, issueId, issue, handleModalClose, onSubmit } = props;
|
|
||||||
const pathname = usePathname();
|
|
||||||
// refs
|
|
||||||
const descriptionEditorRef = useRef<EditorRefApi>(null);
|
|
||||||
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
// store hooks
|
|
||||||
const { captureIssueEvent } = useEventTracker();
|
|
||||||
const { currentProjectDetails } = useProject();
|
|
||||||
const { updateProjectIssue } = useInboxIssues(issueId);
|
|
||||||
const { getWorkspaceBySlug } = useWorkspace();
|
|
||||||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
|
|
||||||
// states
|
|
||||||
const [formSubmitting, setFormSubmitting] = useState(false);
|
|
||||||
const [formData, setFormData] = useState<Partial<TIssue> | undefined>(undefined);
|
|
||||||
const handleFormData = useCallback(
|
|
||||||
<T extends keyof Partial<TIssue>>(issueKey: T, issueValue: Partial<TIssue>[T]) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
[issueKey]: issueValue,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[formData]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (formData?.id != issue?.id)
|
|
||||||
setFormData({
|
|
||||||
id: issue?.id || undefined,
|
|
||||||
name: issue?.name ?? "",
|
|
||||||
description_html: issue?.description_html ?? "<p></p>",
|
|
||||||
priority: issue?.priority ?? "none",
|
|
||||||
state_id: issue?.state_id ?? "",
|
|
||||||
label_ids: issue?.label_ids ?? [],
|
|
||||||
assignee_ids: issue?.assignee_ids ?? [],
|
|
||||||
start_date: renderFormattedPayloadDate(issue?.start_date) ?? "",
|
|
||||||
target_date: renderFormattedPayloadDate(issue?.target_date) ?? "",
|
|
||||||
});
|
|
||||||
}, [issue, formData]);
|
|
||||||
|
|
||||||
const handleFormSubmit = async () => {
|
|
||||||
const payload: Partial<TIssue> = {
|
|
||||||
name: formData?.name || "",
|
|
||||||
description_html: formData?.description_html || "<p></p>",
|
|
||||||
priority: formData?.priority || "none",
|
|
||||||
state_id: formData?.state_id || "",
|
|
||||||
label_ids: formData?.label_ids || [],
|
|
||||||
assignee_ids: formData?.assignee_ids || [],
|
|
||||||
start_date: formData?.start_date || undefined,
|
|
||||||
target_date: formData?.target_date || undefined,
|
|
||||||
cycle_id: formData?.cycle_id || "",
|
|
||||||
module_ids: formData?.module_ids || [],
|
|
||||||
estimate_point: formData?.estimate_point || undefined,
|
|
||||||
parent_id: formData?.parent_id || null,
|
|
||||||
};
|
|
||||||
setFormSubmitting(true);
|
|
||||||
|
|
||||||
onSubmit && (await onSubmit());
|
|
||||||
await updateProjectIssue(payload)
|
|
||||||
.then(async () => {
|
|
||||||
captureIssueEvent({
|
|
||||||
eventName: ISSUE_UPDATED,
|
|
||||||
payload: {
|
|
||||||
...formData,
|
|
||||||
state: "SUCCESS",
|
|
||||||
element: "Inbox page",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.SUCCESS,
|
|
||||||
title: `Success!`,
|
|
||||||
message: "Issue created successfully.",
|
|
||||||
});
|
|
||||||
descriptionEditorRef?.current?.clearEditor();
|
|
||||||
handleModalClose();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
captureIssueEvent({
|
|
||||||
eventName: ISSUE_UPDATED,
|
|
||||||
payload: {
|
|
||||||
...formData,
|
|
||||||
state: "FAILED",
|
|
||||||
element: "Inbox page",
|
|
||||||
},
|
|
||||||
path: pathname,
|
|
||||||
});
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: `Error!`,
|
|
||||||
message: "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setFormSubmitting(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isTitleLengthMoreThan255Character = formData?.name ? formData.name.length > 255 : false;
|
|
||||||
|
|
||||||
if (!workspaceSlug || !projectId || !workspaceId || !formData) return <></>;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="space-y-5 p-5">
|
|
||||||
<h3 className="text-xl font-medium text-custom-text-200">
|
|
||||||
Move {currentProjectDetails?.identifier}-{issue?.sequence_id} to project issues
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<InboxIssueTitle
|
|
||||||
data={formData}
|
|
||||||
handleData={handleFormData}
|
|
||||||
isTitleLengthMoreThan255Character={isTitleLengthMoreThan255Character}
|
|
||||||
/>
|
|
||||||
<InboxIssueDescription
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
workspaceId={workspaceId}
|
|
||||||
data={formData}
|
|
||||||
handleData={handleFormData}
|
|
||||||
editorRef={descriptionEditorRef}
|
|
||||||
containerClassName="border-[0.5px] border-custom-border-200 py-3 min-h-[150px]"
|
|
||||||
onEnterKeyPress={() => submitBtnRef?.current?.click()}
|
|
||||||
/>
|
|
||||||
<InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} isVisible />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
|
|
||||||
<Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
ref={submitBtnRef}
|
|
||||||
loading={formSubmitting}
|
|
||||||
disabled={isTitleLengthMoreThan255Character}
|
|
||||||
onClick={handleFormSubmit}
|
|
||||||
>
|
|
||||||
{formSubmitting ? "Adding" : "Add to project"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
// types
|
|
||||||
import { TIssue } from "@plane/types";
|
|
||||||
// ui
|
|
||||||
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
|
||||||
// components
|
|
||||||
import { InboxIssueCreateRoot, InboxIssueEditRoot } from "@/components/inbox/modals/create-edit-modal";
|
|
||||||
|
|
||||||
type TInboxIssueCreateEditModalRoot = {
|
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
modalState: boolean;
|
|
||||||
handleModalClose: () => void;
|
|
||||||
issue: Partial<TIssue> | undefined;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InboxIssueCreateEditModalRoot: FC<TInboxIssueCreateEditModalRoot> = (props) => {
|
|
||||||
const { workspaceSlug, projectId, modalState, handleModalClose, issue, onSubmit } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalCore
|
|
||||||
isOpen={modalState}
|
|
||||||
handleClose={handleModalClose}
|
|
||||||
position={EModalPosition.TOP}
|
|
||||||
width={EModalWidth.XXXXL}
|
|
||||||
>
|
|
||||||
{issue && issue?.id ? (
|
|
||||||
<InboxIssueEditRoot
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issue.id}
|
|
||||||
issue={issue}
|
|
||||||
handleModalClose={handleModalClose}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InboxIssueCreateRoot workspaceSlug={workspaceSlug} projectId={projectId} handleModalClose={handleModalClose} />
|
|
||||||
)}
|
|
||||||
</ModalCore>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -9,11 +9,7 @@ import { EditorRefApi } from "@plane/editor";
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import {
|
import { InboxIssueTitle, InboxIssueDescription, InboxIssueProperties } from "@/components/inbox/modals/create-modal";
|
||||||
InboxIssueTitle,
|
|
||||||
InboxIssueDescription,
|
|
||||||
InboxIssueProperties,
|
|
||||||
} from "@/components/inbox/modals/create-edit-modal";
|
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_CREATED } from "@/constants/event-tracker";
|
import { ISSUE_CREATED } from "@/constants/event-tracker";
|
||||||
import { ETabIndices } from "@/constants/tab-indices";
|
import { ETabIndices } from "@/constants/tab-indices";
|
||||||
|
|
@ -201,7 +197,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={getIndex("create_more")}
|
tabIndex={getIndex("create_more")}
|
||||||
>
|
>
|
||||||
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
|
<ToggleSwitch value={createMore} onChange={() => { }} size="sm" />
|
||||||
<span className="text-xs">Create more</span>
|
<span className="text-xs">Create more</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
export * from "./create-root";
|
export * from "./create-root";
|
||||||
export * from "./edit-root";
|
|
||||||
export * from "./issue-title";
|
export * from "./issue-title";
|
||||||
export * from "./issue-description";
|
export * from "./issue-description";
|
||||||
export * from "./issue-properties";
|
export * from "./issue-properties";
|
||||||
|
|
@ -91,7 +91,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||||
{/* labels */}
|
{/* labels */}
|
||||||
<div className="h-7">
|
<div className="h-7">
|
||||||
<IssueLabelSelect
|
<IssueLabelSelect
|
||||||
setIsOpen={() => {}}
|
setIsOpen={() => { }}
|
||||||
value={data?.label_ids || []}
|
value={data?.label_ids || []}
|
||||||
onChange={(labelIds) => handleData("label_ids", labelIds)}
|
onChange={(labelIds) => handleData("label_ids", labelIds)}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
27
web/core/components/inbox/modals/create-modal/modal.tsx
Normal file
27
web/core/components/inbox/modals/create-modal/modal.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
// ui
|
||||||
|
import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { InboxIssueCreateRoot } from "@/components/inbox/modals/create-modal";
|
||||||
|
|
||||||
|
type TInboxIssueCreateModalRoot = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
modalState: boolean;
|
||||||
|
handleModalClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxIssueCreateModalRoot: FC<TInboxIssueCreateModalRoot> = (props) => {
|
||||||
|
const { workspaceSlug, projectId, modalState, handleModalClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalCore
|
||||||
|
isOpen={modalState}
|
||||||
|
handleClose={handleModalClose}
|
||||||
|
position={EModalPosition.TOP}
|
||||||
|
width={EModalWidth.XXXXL}
|
||||||
|
>
|
||||||
|
<InboxIssueCreateRoot workspaceSlug={workspaceSlug} projectId={projectId} handleModalClose={handleModalClose} />
|
||||||
|
</ModalCore>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export * from "./create-edit-modal";
|
export * from "./create-modal";
|
||||||
export * from "./decline-issue-modal";
|
export * from "./decline-issue-modal";
|
||||||
export * from "./delete-issue-modal";
|
export * from "./delete-issue-modal";
|
||||||
export * from "./select-duplicate";
|
export * from "./select-duplicate";
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
data,
|
data,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
|
beforeFormSubmit,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
withDraftIssueWrapper = true,
|
withDraftIssueWrapper = true,
|
||||||
storeType: issueStoreFromProps,
|
storeType: issueStoreFromProps,
|
||||||
isDraft = false,
|
isDraft = false,
|
||||||
fetchIssueDetails = true,
|
fetchIssueDetails = true,
|
||||||
moveToIssue = false,
|
moveToIssue = false,
|
||||||
|
modalTitle,
|
||||||
|
primaryButtonText,
|
||||||
} = props;
|
} = props;
|
||||||
const issueStoreType = useIssueStoreType();
|
const issueStoreType = useIssueStoreType();
|
||||||
|
|
||||||
|
|
@ -198,6 +201,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
if (response.id && response.project_id) {
|
if (response.id && response.project_id) {
|
||||||
await handleCreateUpdatePropertyValues({
|
await handleCreateUpdatePropertyValues({
|
||||||
issueId: response.id,
|
issueId: response.id,
|
||||||
|
issueTypeId: response.type_id,
|
||||||
projectId: response.project_id,
|
projectId: response.project_id,
|
||||||
workspaceSlug: workspaceSlug.toString(),
|
workspaceSlug: workspaceSlug.toString(),
|
||||||
});
|
});
|
||||||
|
|
@ -250,6 +254,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
// add other property values
|
// add other property values
|
||||||
await handleCreateUpdatePropertyValues({
|
await handleCreateUpdatePropertyValues({
|
||||||
issueId: data.id,
|
issueId: data.id,
|
||||||
|
issueTypeId: payload.type_id,
|
||||||
projectId: payload.project_id,
|
projectId: payload.project_id,
|
||||||
workspaceSlug: workspaceSlug.toString(),
|
workspaceSlug: workspaceSlug.toString(),
|
||||||
});
|
});
|
||||||
|
|
@ -288,6 +293,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
let response: TIssue | undefined = undefined;
|
let response: TIssue | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (beforeFormSubmit) await beforeFormSubmit();
|
||||||
if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue);
|
if (!data?.id) response = await handleCreateIssue(payload, is_draft_issue);
|
||||||
else response = await handleUpdateIssue(payload);
|
else response = await handleUpdateIssue(payload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -348,6 +354,8 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||||
projectId={activeProjectId}
|
projectId={activeProjectId}
|
||||||
isDraft={isDraft}
|
isDraft={isDraft}
|
||||||
moveToIssue={moveToIssue}
|
moveToIssue={moveToIssue}
|
||||||
|
modalTitle={modalTitle}
|
||||||
|
primaryButtonText={primaryButtonText}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ModalCore>
|
</ModalCore>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export type TCreateUpdatePropertyValuesProps = {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
issueTypeId: string | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueModalContext = {
|
export type TIssueModalContext = {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ export interface DraftIssueProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
isDraft: boolean;
|
isDraft: boolean;
|
||||||
moveToIssue?: boolean;
|
moveToIssue?: boolean;
|
||||||
|
modalTitle?: string;
|
||||||
|
primaryButtonText?: {
|
||||||
|
default: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
|
|
@ -47,6 +52,8 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
onCreateMoreToggleChange,
|
onCreateMoreToggleChange,
|
||||||
isDraft,
|
isDraft,
|
||||||
moveToIssue = false,
|
moveToIssue = false,
|
||||||
|
modalTitle,
|
||||||
|
primaryButtonText,
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
||||||
|
|
@ -132,6 +139,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
if (response && handleCreateUpdatePropertyValues) {
|
if (response && handleCreateUpdatePropertyValues) {
|
||||||
handleCreateUpdatePropertyValues({
|
handleCreateUpdatePropertyValues({
|
||||||
issueId: response.id,
|
issueId: response.id,
|
||||||
|
issueTypeId: response.type_id,
|
||||||
projectId,
|
projectId,
|
||||||
workspaceSlug: workspaceSlug?.toString(),
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
});
|
});
|
||||||
|
|
@ -162,6 +170,8 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
isDraft={isDraft}
|
isDraft={isDraft}
|
||||||
moveToIssue={moveToIssue}
|
moveToIssue={moveToIssue}
|
||||||
|
modalTitle={modalTitle}
|
||||||
|
primaryButtonText={primaryButtonText}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ export interface IssueFormProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
isDraft: boolean;
|
isDraft: boolean;
|
||||||
moveToIssue?: boolean;
|
moveToIssue?: boolean;
|
||||||
|
modalTitle?: string;
|
||||||
|
primaryButtonText?: {
|
||||||
|
default: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
|
|
@ -76,6 +81,11 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
onCreateMoreToggleChange,
|
onCreateMoreToggleChange,
|
||||||
isDraft,
|
isDraft,
|
||||||
moveToIssue,
|
moveToIssue,
|
||||||
|
modalTitle = `${data?.id ? "Update" : isDraft ? "Create a draft" : "Create new issue"}`,
|
||||||
|
primaryButtonText = {
|
||||||
|
default: `${data?.id ? "Update" : isDraft ? "Save to Drafts" : "Save"}`,
|
||||||
|
loading: `${data?.id ? "Updating" : "Saving"}`,
|
||||||
|
},
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// states
|
// states
|
||||||
|
|
@ -185,11 +195,12 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
const submitData = !data?.id
|
const submitData = !data?.id
|
||||||
? formData
|
? formData
|
||||||
: {
|
: {
|
||||||
...getChangedIssuefields(formData, dirtyFields as { [key: string]: boolean | undefined }),
|
...getChangedIssuefields(formData, dirtyFields as { [key: string]: boolean | undefined }),
|
||||||
project_id: getValues<"project_id">("project_id"),
|
project_id: getValues<"project_id">("project_id"),
|
||||||
id: data.id,
|
id: data.id,
|
||||||
description_html: formData.description_html ?? "<p></p>",
|
description_html: formData.description_html ?? "<p></p>",
|
||||||
};
|
type_id: getValues<"type_id">("type_id"),
|
||||||
|
};
|
||||||
|
|
||||||
// this condition helps to move the issues from draft to project issues
|
// this condition helps to move the issues from draft to project issues
|
||||||
if (formData.hasOwnProperty("is_draft")) submitData.is_draft = formData.is_draft;
|
if (formData.hasOwnProperty("is_draft")) submitData.is_draft = formData.is_draft;
|
||||||
|
|
@ -271,9 +282,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
<form onSubmit={handleSubmit((data) => handleFormSubmit(data))}>
|
<form onSubmit={handleSubmit((data) => handleFormSubmit(data))}>
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<h3 className="text-xl font-medium text-custom-text-200 pb-2">
|
<h3 className="text-xl font-medium text-custom-text-200 pb-2">{modalTitle}</h3>
|
||||||
{data?.id ? "Update" : isDraft ? "Create a draft" : "Create new issue"}
|
|
||||||
</h3>
|
|
||||||
{/* Disable project selection if editing an issue */}
|
{/* Disable project selection if editing an issue */}
|
||||||
<div className="flex items-center pt-2 pb-4 gap-x-1">
|
<div className="flex items-center pt-2 pb-4 gap-x-1">
|
||||||
<IssueProjectSelect
|
<IssueProjectSelect
|
||||||
|
|
@ -314,7 +323,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
className={cn(
|
className={cn(
|
||||||
"pb-4 space-y-3",
|
"pb-4 space-y-3",
|
||||||
activeAdditionalPropertiesLength > 4 &&
|
activeAdditionalPropertiesLength > 4 &&
|
||||||
"max-h-[45vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm"
|
"max-h-[45vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="px-5">
|
<div className="px-5">
|
||||||
|
|
@ -343,7 +352,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-5",
|
"px-5",
|
||||||
activeAdditionalPropertiesLength <= 4 &&
|
activeAdditionalPropertiesLength <= 4 &&
|
||||||
"max-h-[25vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm"
|
"max-h-[25vh] overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-sm"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{projectId && (
|
{projectId && (
|
||||||
|
|
@ -384,7 +393,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
tabIndex={getIndex("create_more")}
|
tabIndex={getIndex("create_more")}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
|
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => { }} size="sm" />
|
||||||
<span className="text-xs">Create more</span>
|
<span className="text-xs">Create more</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -415,15 +424,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
tabIndex={isDraft ? getIndex("submit_button") : getIndex("draft_button")}
|
tabIndex={isDraft ? getIndex("submit_button") : getIndex("draft_button")}
|
||||||
>
|
>
|
||||||
{data?.id
|
{isSubmitting ? primaryButtonText.loading : primaryButtonText.default}
|
||||||
? isSubmitting
|
|
||||||
? "Updating"
|
|
||||||
: "Update"
|
|
||||||
: isSubmitting
|
|
||||||
? "Creating"
|
|
||||||
: isDraft
|
|
||||||
? "Save to Drafts"
|
|
||||||
: "Save"}
|
|
||||||
</Button>
|
</Button>
|
||||||
{moveToIssue && (
|
{moveToIssue && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,18 @@ export interface IssuesModalProps {
|
||||||
data?: Partial<TIssue>;
|
data?: Partial<TIssue>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
beforeFormSubmit?: () => Promise<void>;
|
||||||
onSubmit?: (res: TIssue) => Promise<void>;
|
onSubmit?: (res: TIssue) => Promise<void>;
|
||||||
withDraftIssueWrapper?: boolean;
|
withDraftIssueWrapper?: boolean;
|
||||||
storeType?: EIssuesStoreType;
|
storeType?: EIssuesStoreType;
|
||||||
isDraft?: boolean;
|
isDraft?: boolean;
|
||||||
fetchIssueDetails?: boolean;
|
fetchIssueDetails?: boolean;
|
||||||
moveToIssue?: boolean;
|
moveToIssue?: boolean;
|
||||||
|
modalTitle?: string;
|
||||||
|
primaryButtonText?: {
|
||||||
|
default: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export const WorkspaceDraftIssueDeleteIssueModal: React.FC<Props> = (props) => {
|
||||||
isSubmitting={isDeleting}
|
isSubmitting={isDeleting}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Delete draft"
|
title="Delete draft"
|
||||||
content={<>Are you sure you want to delete this draft? This can't be undone.</>}
|
content={<>Are you sure you want to delete this draft? This can't be undone.</>}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export class IssueStore implements IIssueStore {
|
||||||
// if type is archived then check archived_at is not null
|
// if type is archived then check archived_at is not null
|
||||||
// if type is un-archived then check archived_at is null
|
// if type is un-archived then check archived_at is null
|
||||||
const issue = this.issuesMap[issueId];
|
const issue = this.issuesMap[issueId];
|
||||||
if ((issue && type === "archived" && issue.archived_at) || (type === "un-archived" && !issue?.archived_at)) {
|
if (issue && ((type === "archived" && issue.archived_at) || (type === "un-archived" && !issue?.archived_at))) {
|
||||||
filteredIssues.push(issue);
|
filteredIssues.push(issue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue