/** * Copyright (c) 2023-present Plane Software, Inc. and contributors * SPDX-License-Identifier: AGPL-3.0-only * See the LICENSE file for details. */ import { useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; import { CheckCircleFilledIcon, ChevronDownIcon, ChevronUpIcon, CloseCircleFilledIcon, LinkIcon, NewTabIcon } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types"; import { EInboxIssueCurrentTab } from "@plane/types"; import { ControlLink, Header, Row } from "@plane/ui"; import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils"; import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status"; import { useProject } from "@/hooks/store/use-project"; import { useProjectExternalContours } from "@/hooks/store/use-project-external-contours"; import { useAppRouter } from "@/hooks/use-app-router"; import { ExternalContourStatePill } from "./state-pill"; import { ExternalContourDeclineModal } from "./decline-modal"; type Props = { workspaceSlug: string; sourceProjectId: string; contourRequest: TExternalContourRequest; hasDirectTargetAccess: boolean; isSubmitting: TNameDescriptionLoader; }; export const ExternalContoursIssueActionsHeader = observer(function ExternalContoursIssueActionsHeader(props: Props) { const { workspaceSlug, sourceProjectId, contourRequest, hasDirectTargetAccess, isSubmitting, } = props; const { t } = useTranslation(); const router = useAppRouter(); const [isDeclineModalOpen, setIsDeclineModalOpen] = useState(false); const { currentTab, decideRequest, filteredRequestIds, handleCurrentTab, loader } = useProjectExternalContours(); const { getProjectById } = useProject(); const issue = contourRequest.issue; const currentRequestId = contourRequest.id; const hasRelativeNavigation = !!currentRequestId && filteredRequestIds.includes(currentRequestId); const canReviewClosedRequest = contourRequest.capabilities?.can_source_decide ?? (contourRequest.status === "closed" && contourRequest.source_decision !== "accepted"); const isSourceAccepted = contourRequest.source_decision === "accepted"; const redirectToRelativeIssue = useCallback( (direction: "next" | "prev") => { if (!filteredRequestIds || !currentRequestId || !hasRelativeNavigation || filteredRequestIds.length <= 1) return; const currentIssueIndex = filteredRequestIds.findIndex((requestId) => requestId === currentRequestId); if (currentIssueIndex === -1) return; const nextIssueIndex = direction === "next" ? (currentIssueIndex + 1) % filteredRequestIds.length : (currentIssueIndex - 1 + filteredRequestIds.length) % filteredRequestIds.length; const nextIssueId = filteredRequestIds[nextIssueIndex]; if (!nextIssueId) return; router.push(`/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${currentTab}&inboxIssueId=${nextIssueId}`); }, [currentRequestId, currentTab, filteredRequestIds, hasRelativeNavigation, router, sourceProjectId, workspaceSlug] ); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.key === "ArrowUp") redirectToRelativeIssue("prev"); if (event.key === "ArrowDown") redirectToRelativeIssue("next"); }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [redirectToRelativeIssue]); const targetProjectIdentifier = issue.project_detail?.identifier || getProjectById(issue.project_id || "")?.identifier; const workItemLink = generateWorkItemLink({ workspaceSlug: workspaceSlug?.toString(), projectId: issue.project_id, issueId: issue.id, projectIdentifier: targetProjectIdentifier, sequenceId: issue.sequence_id, }); const handleCopyLink = () => copyUrlToClipboard(workItemLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: t("common.link_copied"), message: t("common.copied_to_clipboard"), }) ); const handleDecision = async (action: "accept" | "decline", comment?: string) => { try { await decideRequest(workspaceSlug, sourceProjectId, contourRequest.id, action, comment); if (action === "decline") { setIsDeclineModalOpen(false); await handleCurrentTab(workspaceSlug, sourceProjectId, EInboxIssueCurrentTab.OPEN); router.push( `/${workspaceSlug}/projects/${sourceProjectId}/external-contours?currentTab=${EInboxIssueCurrentTab.OPEN}&inboxIssueId=${contourRequest.id}` ); } setToast({ type: TOAST_TYPE.SUCCESS, title: t("success"), message: t( action === "accept" ? "external_contours_page.actions.accept_success" : "external_contours_page.actions.decline_success" ), }); } catch (error: any) { setToast({ type: TOAST_TYPE.ERROR, title: t("error"), message: error?.error || t("external_contours_page.actions.decision_error"), }); } }; return ( <> setIsDeclineModalOpen(false)} onSubmit={(comment) => handleDecision("decline", comment)} />
{issue?.project_id && issue.sequence_id && (

{targetProjectIdentifier}-{issue.sequence_id}

)}
{canReviewClosedRequest && ( <> )} {isSourceAccepted && (
{t("external_contours_page.traceability.source_decision_accepted")}
)} {hasDirectTargetAccess && ( router.push(workItemLink)} target="_self"> )}
{canReviewClosedRequest && ( <> )} {isSourceAccepted && (
{t("external_contours_page.traceability.source_decision_accepted")}
)} {hasDirectTargetAccess && ( router.push(workItemLink)} target="_self"> )}
); });