import { ChangeEvent, FC, useState, useEffect } from "react"; import Link from "next/link"; import dynamic from "next/dynamic"; import { useRouter } from "next/router"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // services import aiService from "services/ai.service"; // hooks import useToast from "hooks/use-toast"; // components import { GptAssistantModal } from "components/core"; import { IssueAssigneeSelect, IssueDateSelect, IssueEstimateSelect, IssueLabelSelect, IssueParentSelect, IssuePrioritySelect, IssueProjectSelect, IssueStateSelect, } from "components/issues/select"; import { CreateStateModal } from "components/states"; import { CreateUpdateCycleModal } from "components/cycles"; import { CreateLabelModal } from "components/labels"; // ui import { CustomMenu, Input, Loader, PrimaryButton, SecondaryButton, ToggleSwitch, } from "components/ui"; // icons import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline"; // helpers import { cosineSimilarity } from "helpers/string.helper"; // types import type { IIssue } from "types"; // rich-text-editor const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false, loading: () => ( ), }); const defaultValues: Partial = { project: "", name: "", description: { type: "doc", content: [ { type: "paragraph", }, ], }, description_html: "

", estimate_point: null, state: "", cycle: null, priority: null, assignees: [], assignees_list: [], labels: [], labels_list: [], }; export interface IssueFormProps { handleFormSubmit: (values: Partial) => Promise; initialData?: Partial; issues: IIssue[]; projectId: string; setActiveProject: React.Dispatch>; createMore: boolean; setCreateMore: React.Dispatch>; handleClose: () => void; status: boolean; } export const IssueForm: FC = ({ handleFormSubmit, initialData, issues = [], projectId, setActiveProject, createMore, setCreateMore, handleClose, status, }) => { // states const [mostSimilarIssue, setMostSimilarIssue] = useState(); const [cycleModal, setCycleModal] = useState(false); const [stateModal, setStateModal] = useState(false); const [labelModal, setLabelModal] = useState(false); const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; const { setToastAlert } = useToast(); const { register, formState: { errors, isSubmitting }, handleSubmit, reset, watch, control, getValues, setValue, setFocus, } = useForm({ defaultValues, reValidateMode: "onChange", }); const issueName = watch("name"); const handleTitleChange = (e: ChangeEvent) => { const value = e.target.value; const similarIssue = issues?.find((i: IIssue) => cosineSimilarity(i.name, value) > 0.7); setMostSimilarIssue(similarIssue); }; const handleCreateUpdateIssue = async (formData: Partial) => { await handleFormSubmit(formData); reset({ ...defaultValues, project: projectId, description: { type: "doc", content: [ { type: "paragraph", }, ], }, description_html: "

", }); }; const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId) return; setValue("description", {}); setValue("description_html", `${watch("description_html")}

${response}

`); }; const handleAutoGenerateDescription = async () => { if (!workspaceSlug || !projectId) return; setIAmFeelingLucky(true); aiService .createGptTask(workspaceSlug as string, projectId as string, { prompt: issueName, task: "Generate a proper description for this issue in context of a project management software.", }) .then((res) => { if (res.response === "") setToastAlert({ type: "error", title: "Error!", message: "Issue title isn't informative enough to generate the description. Please try with a different title.", }); else handleAiAssistance(res.response_html); }) .catch((err) => { if (err.status === 429) setToastAlert({ type: "error", title: "Error!", message: "You have reached the maximum number of requests of 50 requests per month per user.", }); else setToastAlert({ type: "error", title: "Error!", message: "Some error occurred. Please try again.", }); }) .finally(() => setIAmFeelingLucky(false)); }; useEffect(() => { setFocus("name"); reset({ ...defaultValues, ...initialData, project: projectId, }); }, [setFocus, initialData, reset, projectId]); return ( <> {projectId && ( <> setStateModal(false)} projectId={projectId} /> setCycleModal(false)} /> setLabelModal(false)} projectId={projectId} /> )}
( )} />

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

{watch("parent") && watch("parent") !== "" ? (
i.id === watch("parent"))?.state_detail .color, }} /> {/* {projects?.find((p) => p.id === projectId)?.identifier}- */} {issues.find((i) => i.id === watch("parent"))?.sequence_id} {issues.find((i) => i.id === watch("parent"))?.name.substring(0, 50)} setValue("parent", null)} />
) : null}
{issueName && issueName !== "" && ( )}
( setValue("description", jsonValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} placeholder="Describe the issue..." /> )} /> { setGptAssistantModal(false); // this is done so that the title do not reset after gpt popover closed reset(getValues()); }} inset="top-2 left-0" content="" htmlContent={watch("description_html")} onResponse={handleAiAssistance} projectId={projectId} />
( )} /> ( )} /> ( )} /> ( )} />
( )} />
( )} />
{watch("parent") && watch("parent") !== "" ? ( <> setParentIssueListModalOpen(true)} > Change parent issue setValue("parent", null)} > Remove parent issue ) : ( setParentIssueListModalOpen(true)} > Select Parent Issue )}
setCreateMore((prevData) => !prevData)} > Create more {}} size="md" />
Discard {status ? isSubmitting ? "Updating Issue..." : "Update Issue" : isSubmitting ? "Adding Issue..." : "Add Issue"}
); };