ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: отправка во внешний контур и source-side список

This commit is contained in:
DCCONSTRUCTIONS
2026-04-18 21:47:29 +03:00
parent 390bcdbf38
commit fb33f093de
28 changed files with 911 additions and 386 deletions
@@ -10,8 +10,8 @@ import { observer } from "mobx-react";
import type { EditorRefApi } from "@plane/editor";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TIssue, TNameDescriptionLoader } from "@plane/types";
import { EFileAssetType, EInboxIssueSource } from "@plane/types";
import type { TExternalContourRequest, TIssue, TNameDescriptionLoader } from "@plane/types";
import { EFileAssetType } from "@plane/types";
import { getTextContent } from "@plane/utils";
import { DescriptionVersionsRoot } from "@/components/core/description-versions";
import { DescriptionInput } from "@/components/editor/rich-text/description-input";
@@ -21,38 +21,33 @@ import type { TIssueOperations } from "@/components/issues/issue-detail";
import { IssueActivity } from "@/components/issues/issue-detail/issue-activity";
import { IssueReaction } from "@/components/issues/issue-detail/reactions";
import { IssueTitleInput } from "@/components/issues/title-input";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useMember } from "@/hooks/store/use-member";
import { useProject } from "@/hooks/store/use-project";
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours";
import { useUser } from "@/hooks/store/user";
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe/duplicate-popover";
import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
import { IntakeWorkItemVersionService } from "@/services/inbox";
import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
import { IssueService } from "@/services/issue/issue.service";
import { WorkItemVersionService } from "@/services/issue/work_item_version.service";
import { ExternalContoursIssueContentProperties } from "./issue-properties";
const intakeWorkItemVersionService = new IntakeWorkItemVersionService();
const workItemVersionService = new WorkItemVersionService();
const issueService = new IssueService();
type Props = {
workspaceSlug: string;
projectId: string;
inboxIssue: IInboxIssueStore;
sourceProjectId: string;
contourRequest: TExternalContourRequest;
isEditable: boolean;
isSubmitting: TNameDescriptionLoader;
setIsSubmitting: Dispatch<SetStateAction<TNameDescriptionLoader>>;
};
export const ExternalContoursIssueMainContent = observer(function ExternalContoursIssueMainContent(props: Props) {
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
const { workspaceSlug, sourceProjectId, contourRequest, isEditable, isSubmitting, setIsSubmitting } = props;
const { t } = useTranslation();
const editorRef = useRef<EditorRefApi>(null);
const { data: currentUser } = useUser();
const { getUserDetails } = useMember();
const { loader } = useProjectInbox();
const { getProjectById } = useProject();
const { removeIssue, archiveIssue } = useIssueDetail();
const { loader, updateRequestIssue } = useProjectExternalContours();
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
useEffect(() => {
@@ -64,43 +59,42 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
}
}, [isSubmitting, setIsSubmitting, setShowAlert]);
const issue = inboxIssue.issue;
const projectDetails = issue?.project_id ? getProjectById(issue.project_id) : undefined;
const issue = contourRequest.issue;
const targetProjectId = issue.project_id || sourceProjectId;
const { duplicateIssues } = useDebouncedDuplicateIssues(workspaceSlug, projectDetails?.workspace.toString(), projectId, {
name: issue?.name,
description_html: getTextContent(issue?.description_html),
issueId: issue?.id,
});
const { duplicateIssues } = useDebouncedDuplicateIssues(
workspaceSlug,
targetProjectId,
targetProjectId,
{
name: issue?.name,
description_html: getTextContent(issue?.description_html),
issueId: issue?.id,
}
);
const issueOperations: TIssueOperations = useMemo(
() => ({
fetch: async () => undefined,
remove: async (_workspaceSlug: string, _projectId: string, issueId: string) => {
try {
await removeIssue(workspaceSlug, projectId, issueId);
await issueService.deleteIssue(workspaceSlug, targetProjectId, issueId);
setToast({ title: t("success"), type: TOAST_TYPE.SUCCESS, message: t("inbox_issue.modals.delete.success") });
} catch (error) {
console.log("Error in deleting work item:", error);
} catch {
setToast({ title: t("error"), type: TOAST_TYPE.ERROR, message: t("something_went_wrong_please_try_again") });
}
},
update: async (_workspaceSlug: string, _projectId: string, _issueId: string, data: Partial<TIssue>) => {
update: async (_workspaceSlug: string, _projectId: string, issueId: string, data: Partial<TIssue>) => {
try {
await inboxIssue.updateIssue(data);
const updatedIssue = await issueService.patchIssue(workspaceSlug, targetProjectId, issueId, data);
updateRequestIssue(contourRequest.id, { ...data, ...updatedIssue });
} catch {
setToast({ title: t("error"), type: TOAST_TYPE.ERROR, message: t("issue_could_not_be_updated") });
}
},
archive: async (_workspaceSlug: string, _projectId: string, issueId: string) => {
try {
await archiveIssue(workspaceSlug, projectId, issueId);
} catch (error) {
console.error("Error in archiving issue:", error);
}
},
archive: async () => undefined,
}),
[archiveIssue, inboxIssue, projectId, removeIssue, workspaceSlug]
[contourRequest.id, targetProjectId, t, updateRequestIssue, workspaceSlug]
);
if (!issue || !issue.project_id || !issue.id) return <></>;
@@ -111,16 +105,15 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
{duplicateIssues.length > 0 && (
<DeDupeIssuePopoverRoot
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
projectId={targetProjectId}
rootIssueId={issue.id}
issues={duplicateIssues}
issueOperations={issueOperations}
isIntakeIssue
/>
)}
<IssueTitleInput
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
projectId={targetProjectId}
issueId={issue.id}
isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
@@ -143,12 +136,12 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
initialValue={!issue.description_html || issue.description_html === "" ? "<p></p>" : issue.description_html}
key={issue.id}
onSubmit={async (value, isMigrationUpdate) => {
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
await issueOperations.update(workspaceSlug, targetProjectId, issue.id, {
description_html: value.description_html,
...(isMigrationUpdate ? { skip_activity: "true" } : {}),
});
}}
projectId={issue.project_id}
projectId={targetProjectId}
setIsSubmitting={(value) => setIsSubmitting(value)}
workspaceSlug={workspaceSlug}
/>
@@ -156,27 +149,25 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
<div className="flex items-center justify-between gap-2">
{currentUser && (
<IssueReaction workspaceSlug={workspaceSlug} projectId={projectId} issueId={issue.id} currentUser={currentUser} />
<IssueReaction workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} currentUser={currentUser} />
)}
{isEditable && (
<DescriptionVersionsRoot
className="flex-shrink-0"
entityInformation={{
createdAt: issue.created_at ? new Date(issue.created_at) : new Date(),
createdByDisplayName:
inboxIssue.source === EInboxIssueSource.FORMS
? t("inbox_issue.source.form_user")
: (getUserDetails(issue.created_by ?? "")?.display_name ?? ""),
createdByDisplayName: issue.created_by_detail?.display_name ?? "",
id: issue.id,
isRestoreDisabled: !isEditable,
}}
fetchHandlers={{
listDescriptionVersions: (issueId) => intakeWorkItemVersionService.listDescriptionVersions(workspaceSlug, projectId, issueId),
listDescriptionVersions: (issueId) =>
workItemVersionService.listDescriptionVersions(workspaceSlug, targetProjectId, issueId),
retrieveDescriptionVersion: (issueId, versionId) =>
intakeWorkItemVersionService.retrieveDescriptionVersion(workspaceSlug, projectId, issueId, versionId),
workItemVersionService.retrieveDescriptionVersion(workspaceSlug, targetProjectId, issueId, versionId),
}}
handleRestore={(descriptionHTML) => editorRef.current?.setEditorValue(descriptionHTML, true)}
projectId={projectId}
projectId={targetProjectId}
workspaceSlug={workspaceSlug}
/>
)}
@@ -184,22 +175,21 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou
</div>
<div className="py-4">
<IssueAttachmentRoot workspaceSlug={workspaceSlug} projectId={projectId} issueId={issue.id} disabled={!isEditable} />
<IssueAttachmentRoot workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} disabled={!isEditable} />
</div>
<div className="py-4">
<ExternalContoursIssueContentProperties
workspaceSlug={workspaceSlug}
projectId={projectId}
targetProjectId={targetProjectId}
issue={issue}
issueOperations={issueOperations}
isEditable={isEditable}
duplicateIssueDetails={inboxIssue.duplicate_issue_detail}
/>
</div>
<div className="pt-4">
<IssueActivity workspaceSlug={workspaceSlug} projectId={projectId} issueId={issue.id} isIntakeIssue />
<IssueActivity workspaceSlug={workspaceSlug} projectId={targetProjectId} issueId={issue.id} />
</div>
</>
);