import React, { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { mutate } from "swr"; // react-beautiful-dnd import { DraggableProvided, DraggableStateSnapshot, DraggingStyle, NotDraggingStyle, } from "react-beautiful-dnd"; // services import issuesService from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // components import { ViewAssigneeSelect, ViewDueDateSelect, ViewEstimateSelect, ViewIssueLabel, ViewPrioritySelect, ViewStartDateSelect, ViewStateSelect, } from "components/issues"; // ui import { ContextMenu, CustomMenu, Tooltip } from "components/ui"; // icons import { ClipboardDocumentCheckIcon, LinkIcon, PencilIcon, TrashIcon, XMarkIcon, ArrowTopRightOnSquareIcon, PaperClipIcon, EllipsisHorizontalIcon, } from "@heroicons/react/24/outline"; import { LayerDiagonalIcon } from "components/icons"; // helpers import { handleIssuesMutation } from "constants/issue"; import { copyTextToClipboard } from "helpers/string.helper"; // types import { ICurrentUserResponse, IIssue, IIssueViewProps, ISubIssueResponse, UserAuth } from "types"; // fetch-keys import { CYCLE_DETAILS, MODULE_DETAILS, SUB_ISSUES } from "constants/fetch-keys"; type Props = { type?: string; provided: DraggableProvided; snapshot: DraggableStateSnapshot; issue: IIssue; groupTitle?: string; index: number; editIssue: () => void; makeIssueCopy: () => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; handleTrashBox: (isDragging: boolean) => void; disableUserActions: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; viewProps: IIssueViewProps; }; export const SingleBoardIssue: React.FC = ({ type, provided, snapshot, issue, index, editIssue, makeIssueCopy, removeIssue, groupTitle, handleDeleteIssue, handleTrashBox, disableUserActions, user, userAuth, viewProps, }) => { // context menu const [contextMenu, setContextMenu] = useState(false); const [contextMenuPosition, setContextMenuPosition] = useState(null); const [isMenuActive, setIsMenuActive] = useState(false); const [isDropdownActive, setIsDropdownActive] = useState(false); const actionSectionRef = useRef(null); const { groupByProperty: selectedGroup, orderBy, properties, mutateIssues } = viewProps; const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !issue) return; if (issue.parent) { mutate( SUB_ISSUES(issue.parent.toString()), (prevData) => { if (!prevData) return prevData; return { ...prevData, sub_issues: (prevData.sub_issues ?? []).map((i) => { if (i.id === issue.id) { return { ...i, ...formData, }; } return i; }), }; }, false ); } else { mutateIssues( (prevData: any) => handleIssuesMutation( formData, groupTitle ?? "", selectedGroup, index, orderBy, prevData ), false ); } issuesService .patchIssue(workspaceSlug as string, issue.project, issue.id, formData, user) .then(() => { mutateIssues(); if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); }); }, [ workspaceSlug, cycleId, moduleId, groupTitle, index, selectedGroup, mutateIssues, orderBy, user, ] ); const getStyle = ( style: DraggingStyle | NotDraggingStyle | undefined, snapshot: DraggableStateSnapshot ) => { if (orderBy === "sort_order") return style; if (!snapshot.isDragging) return {}; if (!snapshot.isDropAnimating) return style; return { ...style, transitionDuration: `0.001s`, }; }; const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard( `${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}` ).then(() => { setToastAlert({ type: "success", title: "Link Copied!", message: "Issue link copied to clipboard.", }); setIsMenuActive(false); }); }; useEffect(() => { if (snapshot.isDragging) handleTrashBox(snapshot.isDragging); }, [snapshot, handleTrashBox]); useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; return ( <> {!isNotAllowed && ( <> Edit issue Make a copy... handleDeleteIssue(issue)}> Delete issue )} Copy issue link Open issue in new tab
{ e.preventDefault(); setContextMenu(true); setContextMenuPosition(e); }} >
{!isNotAllowed && ( )} {properties.key && (
{issue.project_detail.identifier}-{issue.sequence_id}
)}
{issue.name}
{properties.priority && ( )} {properties.state && ( )} {properties.start_date && issue.start_date && ( setIsDropdownActive(true)} handleOnClose={() => setIsDropdownActive(false)} user={user} isNotAllowed={isNotAllowed} /> )} {properties.due_date && issue.target_date && ( setIsDropdownActive(true)} handleOnClose={() => setIsDropdownActive(false)} user={user} isNotAllowed={isNotAllowed} /> )} {properties.labels && issue.labels.length > 0 && ( )} {properties.assignee && ( )} {properties.estimate && issue.estimate_point !== null && ( )} {properties.sub_issue_count && issue.sub_issues_count > 0 && (
{issue.sub_issues_count}
)} {properties.link && issue.link_count > 0 && (
{issue.link_count}
)} {properties.attachment_count && issue.attachment_count > 0 && (
{issue.attachment_count}
)}
); };