import { FC, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // headless ui import { Disclosure, Transition } from "@headlessui/react"; // services import issuesService from "services/issues.service"; // contexts import { useProjectMyMembership } from "contexts/project-member.context"; // components import { ExistingIssuesListModal } from "components/core"; import { CreateUpdateIssueModal } from "components/issues"; // ui import { CustomMenu } from "components/ui"; // icons import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; // helpers import { orderArrayBy } from "helpers/array.helper"; // types import { ICurrentUserResponse, IIssue, ISearchIssueResponse, ISubIssueResponse } from "types"; // fetch-keys import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys"; type Props = { parentIssue: IIssue; user: ICurrentUserResponse | undefined; disabled?: boolean; }; export const SubIssuesList: FC = ({ parentIssue, user, disabled = false }) => { // states const [createIssueModal, setCreateIssueModal] = useState(false); const [subIssuesListModal, setSubIssuesListModal] = useState(false); const [preloadedData, setPreloadedData] = useState | null>(null); const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; const { memberRole } = useProjectMyMembership(); const { data: subIssuesResponse } = useSWR( workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null, workspaceSlug && projectId && issueId ? () => issuesService.subIssues(workspaceSlug as string, projectId as string, issueId as string) : null ); const { data: issues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, workspaceSlug && projectId ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) : null ); const addAsSubIssue = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; const payload = { sub_issue_ids: data.map((i) => i.id), }; await issuesService .addSubIssues(workspaceSlug as string, projectId as string, parentIssue?.id ?? "", payload) .then(() => { mutate( SUB_ISSUES(parentIssue?.id ?? ""), (prevData) => { if (!prevData) return prevData; let newSubIssues = prevData.sub_issues as IIssue[]; const stateDistribution = { ...prevData.state_distribution }; payload.sub_issue_ids.forEach((issueId: string) => { const issue = issues?.find((i) => i.id === issueId); if (issue) { newSubIssues.push(issue); const issueGroup = issue.state_detail.group; stateDistribution[issueGroup] = stateDistribution[issueGroup] + 1; } }); newSubIssues = orderArrayBy(newSubIssues, "created_at", "descending"); return { state_distribution: stateDistribution, sub_issues: newSubIssues, }; }, false ); mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (prevData) => (prevData ?? []).map((p) => { if (payload.sub_issue_ids.includes(p.id)) return { ...p, parent: parentIssue.id, }; return p; }), false ); mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); }) .catch((err) => { console.log(err); }); }; const handleSubIssueRemove = (issueId: string) => { if (!workspaceSlug || !projectId) return; mutate( SUB_ISSUES(parentIssue.id ?? ""), (prevData) => { if (!prevData) return prevData; const updatedArray = (prevData.sub_issues ?? []).filter((i) => i.id !== issueId); const stateDistribution = { ...prevData.state_distribution }; const issueGroup = issues?.find((i) => i.id === issueId)?.state_detail.group ?? "backlog"; stateDistribution[issueGroup] = stateDistribution[issueGroup] - 1; return { state_distribution: stateDistribution, sub_issues: updatedArray, }; }, false ); issuesService .patchIssue(workspaceSlug.toString(), projectId.toString(), issueId, { parent: null }, user) .then((res) => { mutate(SUB_ISSUES(parentIssue.id ?? "")); mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (prevData) => (prevData ?? []).map((p) => { if (p.id === res.id) return { ...p, ...res, }; return p; }), false ); }) .catch((e) => { console.error(e); }); }; const handleCreateIssueModal = () => { setCreateIssueModal(true); setPreloadedData({ parent: parentIssue.id, }); }; const completedSubIssues = subIssuesResponse ? subIssuesResponse?.state_distribution.completed + subIssuesResponse?.state_distribution.cancelled : 0; const totalSubIssues = subIssuesResponse && subIssuesResponse.sub_issues ? subIssuesResponse?.sub_issues.length : 0; const completionPercentage = (completedSubIssues / totalSubIssues) * 100; const isNotAllowed = memberRole.isGuest || memberRole.isViewer || disabled; return ( <> setCreateIssueModal(false)} /> setSubIssuesListModal(false)} searchParams={{ sub_issue: true, issue_id: parentIssue?.id }} handleOnSubmit={addAsSubIssue} /> {subIssuesResponse && subIssuesResponse.sub_issues && subIssuesResponse.sub_issues.length > 0 ? ( {({ open }) => ( <>
Sub-issues{" "} {subIssuesResponse.sub_issues.length} {subIssuesResponse.state_distribution && (
100 ? 100 : completionPercentage.toFixed(0) }%`, }} />
{isNaN(completionPercentage) ? 0 : completionPercentage > 100 ? 100 : completionPercentage.toFixed(0)} % Done
)}
{open && !isNotAllowed ? (
setSubIssuesListModal(true)}> Add an existing issue
) : null}
{subIssuesResponse.sub_issues.map((issue) => (
{issue.project_detail.identifier}-{issue.sequence_id} {issue.name}
{!isNotAllowed && ( )}
))}
)} ) : ( !isNotAllowed && ( Add sub-issue } position="left" noBorder noChevron > Create new setSubIssuesListModal(true)}> Add an existing issue ) )} ); };