import React, { useEffect, useState } from "react"; import Link from "next/link"; import dynamic from "next/dynamic"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; import { Controller, useForm } from "react-hook-form"; import { Dialog, Menu, Transition } from "@headlessui/react"; // services import issuesServices from "lib/services/issues.service"; // hooks import useUser from "lib/hooks/useUser"; import useToast from "lib/hooks/useToast"; // ui import { Button, Input, Loader } from "ui"; // icons import { EllipsisHorizontalIcon, XMarkIcon } from "@heroicons/react/24/outline"; // components import SelectState from "components/project/issues/create-update-issue-modal/select-state"; import SelectCycles from "components/project/issues/create-update-issue-modal/select-cycle"; import SelectLabels from "components/project/issues/create-update-issue-modal/select-labels"; import SelectProject from "components/project/issues/create-update-issue-modal/select-project"; import SelectPriority from "components/project/issues/create-update-issue-modal/select-priority"; import SelectAssignee from "components/project/issues/create-update-issue-modal/select-assignee"; import SelectParent from "components/project/issues/create-update-issue-modal/select-parent-issue"; import CreateUpdateStateModal from "components/project/issues/BoardView/state/create-update-state-modal"; import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal"; // types import type { IIssue, IssueResponse } from "types"; // fetch keys import { PROJECT_ISSUES_DETAILS, PROJECT_ISSUES_LIST, CYCLE_ISSUES, USER_ISSUE, PROJECTS_LIST, MODULE_ISSUES, } from "constants/fetch-keys"; // common import { renderDateFormat, cosineSimilarity } from "constants/common"; import projectService from "lib/services/project.service"; import modulesService from "lib/services/modules.service"; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false, loading: () => ( ), }); type Props = { isOpen: boolean; setIsOpen: React.Dispatch>; projectId?: string; data?: IIssue; prePopulateData?: Partial; isUpdatingSingleIssue?: boolean; }; const defaultValues: Partial = { project: "", name: "", description: "", description_html: "

", state: "", cycle: null, priority: null, labels_list: [], }; const CreateUpdateIssuesModal: React.FC = ({ isOpen, setIsOpen, data, projectId, prePopulateData, isUpdatingSingleIssue = false, }) => { const [createMore, setCreateMore] = useState(false); const [isCycleModalOpen, setIsCycleModalOpen] = useState(false); const [isStateModalOpen, setIsStateModalOpen] = useState(false); const [activeProject, setActiveProject] = useState(null); const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); const [mostSimilarIssue, setMostSimilarIssue] = useState(); const router = useRouter(); const { workspaceSlug } = router.query; const { user } = useUser(); const { setToastAlert } = useToast(); const { data: issues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId) : null, workspaceSlug && projectId ? () => issuesServices.getIssues(workspaceSlug as string, projectId) : null ); const { data: projects } = useSWR( workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null, workspaceSlug ? () => projectService.getProjects(workspaceSlug as string) : null ); const { register, formState: { errors, isSubmitting }, handleSubmit, reset, setError, control, watch, setValue, } = useForm({ defaultValues, mode: "all", reValidateMode: "onChange", }); useEffect(() => { if (data) setIsOpen(true); }, [data, setIsOpen]); useEffect(() => { if (projects && projects.length > 0) setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null); }, [projectId, projects]); useEffect(() => { reset({ ...defaultValues, ...watch(), ...data, project: activeProject ?? "", ...prePopulateData, }); }, [data, prePopulateData, reset, activeProject, isOpen, watch]); useEffect(() => { return () => setMostSimilarIssue(undefined); }, []); const resetForm = () => { reset({ ...defaultValues, project: activeProject ?? undefined }); }; const handleClose = () => { setIsOpen(false); if (data) { resetForm(); } }; const addIssueToCycle = async (issueId: string, cycleId: string) => { if (!workspaceSlug || !projectId) return; await issuesServices .addIssueToCycle(workspaceSlug as string, projectId, cycleId, { issues: [issueId], }) .then((res) => { mutate(CYCLE_ISSUES(cycleId)); if (isUpdatingSingleIssue) { mutate( PROJECT_ISSUES_DETAILS, (prevData) => ({ ...(prevData as IIssue), sprints: cycleId }), false ); } else mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId), (prevData) => { return { ...(prevData as IssueResponse), results: (prevData?.results ?? []).map((issue) => { if (issue.id === res.id) return { ...issue, sprints: cycleId }; return issue; }), }; }, false ); }) .catch((err) => { console.log(err); }); }; const addIssueToModule = async (issueId: string, moduleId: string) => { if (!workspaceSlug || !projectId) return; await modulesService .addIssuesToModule(workspaceSlug as string, projectId, moduleId as string, { issues: [issueId], }) .then((res) => { console.log(res); mutate(MODULE_ISSUES(moduleId as string)); }) .catch((e) => console.log(e)); }; const onSubmit = async (formData: IIssue) => { if (!workspaceSlug || !projectId) return; const payload: Partial = { ...formData, target_date: formData.target_date ? renderDateFormat(formData.target_date ?? "") : null, }; if (!data) { await issuesServices .createIssues(workspaceSlug as string, projectId, payload) .then((res) => { mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId)); if (formData.cycle && formData.cycle !== null) { addIssueToCycle(res.id, formData.cycle); } if (formData.module && formData.module !== null) { addIssueToModule(res.id, formData.module); } resetForm(); if (!createMore) handleClose(); setToastAlert({ title: "Success", type: "success", message: `Issue ${data ? "updated" : "created"} successfully`, }); if (formData.assignees_list.some((assignee) => assignee === user?.id)) { mutate(USER_ISSUE); } }) .catch((err) => { if (err.detail) { setToastAlert({ title: "Join the project.", type: "error", message: "Click select to join from projects page to start making changes", }); } Object.keys(err).map((key) => { const message = err[key]; if (!message) return; setError(key as keyof IIssue, { message: Array.isArray(message) ? message.join(", ") : message, }); }); }); } else { await issuesServices .updateIssue(workspaceSlug as string, projectId, data.id, payload) .then((res) => { if (isUpdatingSingleIssue) { mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); } else mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId), (prevData) => { return { ...(prevData as IssueResponse), results: (prevData?.results ?? []).map((issue) => { if (issue.id === res.id) return { ...issue, ...res }; return issue; }), }; } ); if (formData.cycle && formData.cycle !== null) { addIssueToCycle(res.id, formData.cycle); } resetForm(); if (!createMore) handleClose(); setToastAlert({ title: "Success", type: "success", message: "Issue updated successfully", }); }) .catch((err) => { Object.keys(err).map((key) => { setError(key as keyof IIssue, { message: err[key].join(", ") }); }); }); } }; return ( <> {projectId && ( <> setIsStateModalOpen(false)} projectId={projectId} /> )}

{data ? "Update" : "Create"} Issue

{watch("parent") && watch("parent") !== "" ? (
i.id === watch("parent") )?.state_detail.color, }} /> {projects?.find((p) => p.id === activeProject)?.identifier}- {issues?.results.find((i) => i.id === watch("parent"))?.sequence_id} {issues?.results .find((i) => i.id === watch("parent")) ?.name.substring(0, 50)} setValue("parent", null)} />
) : null}
{ const value = e.target.value; const similarIssue = issues?.results.find( (i) => cosineSimilarity(i.name, value) > 0.7 ); setMostSimilarIssue(similarIssue?.id); }} className="resize-none" placeholder="Enter title" autoComplete="off" error={errors.name} register={register} validations={{ required: "Name is required", maxLength: { value: 255, message: "Name should be less than 255 characters", }, }} /> {mostSimilarIssue && ( )}
( { setValue("description", jsonValue); setValue("description_html", htmlValue); }} placeholder="Enter Your Text..." /> )} />
( { onChange(e.target.value); }} className="cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" /> )} />
{watch("parent") && watch("parent") !== "" ? ( <> ) : ( )}
setCreateMore((prevData) => !prevData)} > Create more
); }; export default CreateUpdateIssuesModal;