import { FC, useCallback, useState } from "react"; import { useRouter } from "next/router"; import { mutate } from "swr"; import { Popover2 } from "@blueprintjs/popover2"; // components import { ViewDueDateSelect, ViewEstimateSelect, ViewStartDateSelect } from "components/issues"; import { LabelSelect, MembersSelect, PrioritySelect } from "components/project"; import { StateSelect } from "components/states"; // icons import { MoreHorizontal, LinkIcon, Pencil, Trash2, ChevronRight } from "lucide-react"; // services import { IssueService } from "services/issue"; import { TrackEventService } from "services/track_event.service"; // constant import { CYCLE_DETAILS, CYCLE_ISSUES_WITH_PARAMS, MODULE_DETAILS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, SUB_ISSUES, VIEW_ISSUES, } from "constants/fetch-keys"; // types import { IUser, IIssue, IState, ISubIssueResponse, Properties, TIssuePriorities, UserAuth } from "types"; // helper import { copyTextToClipboard } from "helpers/string.helper"; import { renderLongDetailDateFormat } from "helpers/date-time.helper"; // hooks import useToast from "hooks/use-toast"; type Props = { issue: IIssue; projectId: string; index: number; expanded: boolean; handleToggleExpand: (issueId: string) => void; properties: Properties; handleEditIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void; gridTemplateColumns: string; disableUserActions: boolean; user: IUser | undefined; userAuth: UserAuth; nestingLevel: number; }; const issueService = new IssueService(); const trackEventService = new TrackEventService(); export const SingleSpreadsheetIssue: FC = (props) => { const { issue, projectId, index, expanded, handleToggleExpand, properties, handleEditIssue, handleDeleteIssue, gridTemplateColumns, disableUserActions, user, userAuth, nestingLevel, } = props; // states const [isOpen, setIsOpen] = useState(false); // router const router = useRouter(); const { workspaceSlug, cycleId, moduleId, viewId } = router.query; const params = {}; const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !projectId) return; const fetchKey = cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : viewId ? VIEW_ISSUES(viewId.toString(), params) : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params); 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 mutate( fetchKey, (prevData) => (prevData ?? []).map((p) => { if (p.id === issue.id) { return { ...p, ...formData, }; } return p; }), false ); issueService .patchIssue(workspaceSlug as string, projectId, issue.id as string, formData, user) .then(() => { if (issue.parent) { mutate(SUB_ISSUES(issue.parent as string)); } else { if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); } }) .catch((error) => { console.log(error); }); }, // eslint-disable-next-line react-hooks/exhaustive-deps [workspaceSlug, projectId, cycleId, moduleId, user] ); const openPeekOverview = () => { const { query } = router; router.push({ pathname: router.pathname, query: { ...query, peekIssue: issue.id }, }); }; 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.", }); }); }; const handleStateChange = (data: string, states: IState[] | undefined) => { const oldState = states?.find((s) => s.id === issue.state); const newState = states?.find((s) => s.id === data); partialUpdateIssue( { state: data, state_detail: newState, }, issue ); trackEventService.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, workspaceId: issue.workspace, projectId: issue.project_detail.id, projectIdentifier: issue.project_detail.identifier, projectName: issue.project_detail.name, issueId: issue.id, }, "ISSUE_PROPERTY_UPDATE_STATE", user as IUser ); if (oldState?.group !== "completed" && newState?.group !== "completed") { trackEventService.trackIssueMarkedAsDoneEvent( { workspaceSlug: issue.workspace_detail.slug, workspaceId: issue.workspace_detail.id, projectId: issue.project_detail.id, projectIdentifier: issue.project_detail.identifier, projectName: issue.project_detail.name, issueId: issue.id, }, user as IUser ); } }; const handlePriorityChange = (data: TIssuePriorities) => { partialUpdateIssue({ priority: data }, issue); trackEventService.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, workspaceId: issue.workspace, projectId: issue.project_detail.id, projectIdentifier: issue.project_detail.identifier, projectName: issue.project_detail.name, issueId: issue.id, }, "ISSUE_PROPERTY_UPDATE_PRIORITY", user as IUser ); }; const handleAssigneeChange = (data: any) => { const newData = issue.assignees ?? []; if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); else newData.push(data); partialUpdateIssue({ assignees_list: data }, issue); trackEventService.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, workspaceId: issue.workspace, projectId: issue.project_detail.id, projectIdentifier: issue.project_detail.identifier, projectName: issue.project_detail.name, issueId: issue.id, }, "ISSUE_PROPERTY_UPDATE_ASSIGNEE", user as IUser ); }; const handleLabelChange = (data: any) => { partialUpdateIssue({ labels_list: data }, issue); }; const paddingLeft = `${nestingLevel * 68}px`; const tooltipPosition = index === 0 ? "bottom" : "top"; const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( <>
{properties.key && ( {issue.project_detail?.identifier}-{issue.sequence_id} )} {!isNotAllowed && !disableUserActions && (
setIsOpen(nextOpenState)} content={
} placement="bottom-start" >
)}
{issue.sub_issues_count > 0 && (
)}
{properties.state && (
)} {properties.priority && (
)} {properties.assignee && (
)} {properties.labels && (
)} {properties.start_date && (
)} {properties.due_date && (
{user && ( )}
)} {properties.estimate && (
)} {properties.created_on && (
{renderLongDetailDateFormat(issue.created_at)}
)} {properties.updated_on && (
{renderLongDetailDateFormat(issue.updated_at)}
)}
); };