import React, { useCallback, useEffect, 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 useIssuesView from "hooks/use-issues-view"; import useToast from "hooks/use-toast"; // components import { ViewAssigneeSelect, ViewDueDateSelect, ViewEstimateSelect, ViewPrioritySelect, ViewStateSelect, } from "components/issues"; // ui import { ContextMenu, CustomMenu } from "components/ui"; // icons import { ClipboardDocumentCheckIcon, LinkIcon, PencilIcon, TrashIcon, XMarkIcon, ArrowTopRightOnSquareIcon, } from "@heroicons/react/24/outline"; // helpers import { handleIssuesMutation } from "constants/issue"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types import { IIssue, Properties, TIssueGroupByOptions, UserAuth } from "types"; // fetch-keys import { CYCLE_DETAILS, CYCLE_ISSUES_WITH_PARAMS, MODULE_DETAILS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, } from "constants/fetch-keys"; import useEstimateOption from "hooks/use-estimate-option"; type Props = { type?: string; provided: DraggableProvided; snapshot: DraggableStateSnapshot; issue: IIssue; properties: Properties; groupTitle?: string; index: number; selectedGroup: TIssueGroupByOptions; editIssue: () => void; makeIssueCopy: () => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; handleTrashBox: (isDragging: boolean) => void; isCompleted?: boolean; userAuth: UserAuth; }; export const SingleBoardIssue: React.FC = ({ type, provided, snapshot, issue, properties, index, selectedGroup, editIssue, makeIssueCopy, removeIssue, groupTitle, handleDeleteIssue, handleTrashBox, isCompleted = false, userAuth, }) => { // context menu const [contextMenu, setContextMenu] = useState(false); const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 }); const { orderBy, params } = useIssuesView(); const { estimateValue } = useEstimateOption(issue.estimate_point); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( (formData: Partial) => { if (!workspaceSlug || !projectId) return; if (cycleId) mutate< | { [key: string]: IIssue[]; } | IIssue[] >( CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params), (prevData) => handleIssuesMutation(formData, groupTitle ?? "", selectedGroup, index, prevData), false ); else if (moduleId) mutate< | { [key: string]: IIssue[]; } | IIssue[] >( MODULE_ISSUES_WITH_PARAMS(moduleId as string), (prevData) => handleIssuesMutation(formData, groupTitle ?? "", selectedGroup, index, prevData), false ); else mutate< | { [key: string]: IIssue[]; } | IIssue[] >( PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params), (prevData) => handleIssuesMutation(formData, groupTitle ?? "", selectedGroup, index, prevData), false ); issuesService .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) .then(() => { if (cycleId) { mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); mutate(CYCLE_DETAILS(cycleId as string)); } else if (moduleId) { mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); mutate(MODULE_DETAILS(moduleId as string)); } else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); }) .catch((error) => { console.log(error); }); }, [workspaceSlug, projectId, cycleId, moduleId, issue, groupTitle, index, selectedGroup, params] ); 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.", }); }); }; useEffect(() => { if (snapshot.isDragging) handleTrashBox(snapshot.isDragging); }, [snapshot, handleTrashBox]); const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted; return ( <> {!isNotAllowed && ( <> Edit issue Make a copy... handleDeleteIssue(issue)}> Delete issue )} Copy issue link Open issue in new tab
{ e.preventDefault(); setContextMenu(true); setContextMenuPosition({ x: e.pageX, y: e.pageY }); }} >
{!isNotAllowed && (
{type && !isNotAllowed && (
Edit issue
{type !== "issue" && removeIssue && (
Remove from {type}
)} handleDeleteIssue(issue)}>
Delete issue
Copy issue Link
)}
)} {properties.key && (
{issue.project_detail.identifier}-{issue.sequence_id}
)}
{truncateText(issue.name, 100)}
{properties.priority && ( )} {properties.state && ( )} {properties.due_date && ( )} {properties.sub_issue_count && (
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
)} {properties.labels && issue.label_details.length > 0 && (
{issue.label_details.map((label) => (
{label.name}
))}
)} {properties.assignee && ( )} {properties.estimate && ( )}
); };