From 49a895f1174d6c4ca9a4d1f7e924b005daa832ae Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Wed, 7 Aug 2024 20:54:08 +0530 Subject: [PATCH] improvement: merge quick add logic for all layouts. (#5323) --- web/ce/components/issues/index.ts | 1 + web/ce/components/issues/quick-add/index.ts | 1 + web/ce/components/issues/quick-add/root.tsx | 74 +++++ .../issues/issue-layouts/calendar/index.ts | 2 +- .../issue-layouts/calendar/issue-blocks.tsx | 6 +- .../calendar/quick-add-issue-actions.tsx | 133 +++++++++ .../calendar/quick-add-issue-form.tsx | 264 ------------------ .../issue-layouts/gantt/base-gantt-root.tsx | 18 +- .../issues/issue-layouts/gantt/index.ts | 2 +- .../gantt/quick-add-issue-form.tsx | 179 ------------ .../components/issues/issue-layouts/index.ts | 4 + .../issues/issue-layouts/kanban/index.ts | 1 - .../issue-layouts/kanban/kanban-group.tsx | 11 +- .../kanban/quick-add-issue-form.tsx | 162 ----------- .../issues/issue-layouts/list/index.ts | 1 - .../issues/issue-layouts/list/list-group.tsx | 9 +- .../list/quick-add-issue-form.tsx | 168 ----------- .../issue-layouts/quick-add/button/gantt.tsx | 19 ++ .../issue-layouts/quick-add/button/index.ts | 4 + .../issue-layouts/quick-add/button/kanban.tsx | 18 ++ .../issue-layouts/quick-add/button/list.tsx | 18 ++ .../quick-add/button/spreadsheet.tsx | 21 ++ .../issue-layouts/quick-add/form/calendar.tsx | 31 ++ .../issue-layouts/quick-add/form/gantt.tsx | 32 +++ .../issue-layouts/quick-add/form/index.ts | 5 + .../issue-layouts/quick-add/form/kanban.tsx | 26 ++ .../issue-layouts/quick-add/form/list.tsx | 31 ++ .../quick-add/form/spreadsheet.tsx | 31 ++ .../issues/issue-layouts/quick-add/index.ts | 3 + .../issues/issue-layouts/quick-add/root.tsx | 185 ++++++++++++ .../issues/issue-layouts/spreadsheet/index.ts | 1 - .../spreadsheet/quick-add-issue-form.tsx | 238 ---------------- .../spreadsheet/spreadsheet-view.tsx | 9 +- web/core/components/issues/title-input.tsx | 2 +- web/ee/components/issues/index.ts | 1 + web/ee/components/issues/quick-add/index.ts | 1 + web/ee/components/issues/quick-add/root.tsx | 1 + 37 files changed, 680 insertions(+), 1033 deletions(-) create mode 100644 web/ce/components/issues/quick-add/index.ts create mode 100644 web/ce/components/issues/quick-add/root.tsx create mode 100644 web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx delete mode 100644 web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx delete mode 100644 web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx delete mode 100644 web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx delete mode 100644 web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/button/index.ts create mode 100644 web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/button/list.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/index.ts create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/list.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx create mode 100644 web/core/components/issues/issue-layouts/quick-add/index.ts create mode 100644 web/core/components/issues/issue-layouts/quick-add/root.tsx delete mode 100644 web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx create mode 100644 web/ee/components/issues/quick-add/index.ts create mode 100644 web/ee/components/issues/quick-add/root.tsx diff --git a/web/ce/components/issues/index.ts b/web/ce/components/issues/index.ts index 82d35e3aa..3bc3bdbfd 100644 --- a/web/ce/components/issues/index.ts +++ b/web/ce/components/issues/index.ts @@ -2,3 +2,4 @@ export * from "./bulk-operations"; export * from "./worklog"; export * from "./issue-modal"; export * from "./issue-details"; +export * from "./quick-add"; diff --git a/web/ce/components/issues/quick-add/index.ts b/web/ce/components/issues/quick-add/index.ts new file mode 100644 index 000000000..1efe34c51 --- /dev/null +++ b/web/ce/components/issues/quick-add/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/ce/components/issues/quick-add/root.tsx b/web/ce/components/issues/quick-add/root.tsx new file mode 100644 index 000000000..8ca22c386 --- /dev/null +++ b/web/ce/components/issues/quick-add/root.tsx @@ -0,0 +1,74 @@ +import { FC, useEffect, useRef } from "react"; +import { observer } from "mobx-react"; +import { UseFormRegister, UseFormSetFocus } from "react-hook-form"; +// types +import { TIssue } from "@plane/types"; +// components +import { + CalendarQuickAddIssueForm, + GanttQuickAddIssueForm, + KanbanQuickAddIssueForm, + ListQuickAddIssueForm, + SpreadsheetQuickAddIssueForm, + TQuickAddIssueForm, +} from "@/components/issues/issue-layouts"; +// constants +import { EIssueLayoutTypes } from "@/constants/issue"; +// hooks +import { useProject } from "@/hooks/store"; +import useKeypress from "@/hooks/use-keypress"; +import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; + +export type TQuickAddIssueFormRoot = { + isOpen: boolean; + layout: EIssueLayoutTypes; + prePopulatedData?: Partial; + projectId: string; + hasError?: boolean; + setFocus: UseFormSetFocus; + register: UseFormRegister; + onSubmit: () => void; + onClose: () => void; +}; + +export const QuickAddIssueFormRoot: FC = observer((props) => { + const { isOpen, layout, projectId, hasError = false, setFocus, register, onSubmit, onClose } = props; + // store hooks + const { getProjectById } = useProject(); + // derived values + const projectDetail = getProjectById(projectId); + // refs + const ref = useRef(null); + // click detection + useKeypress("Escape", onClose); + useOutsideClickDetector(ref, onClose); + // set focus on name input + useEffect(() => { + setFocus("name"); + }, [setFocus]); + + if (!projectDetail) return <>; + + const QUICK_ADD_ISSUE_FORMS: Record> = { + [EIssueLayoutTypes.LIST]: ListQuickAddIssueForm, + [EIssueLayoutTypes.KANBAN]: KanbanQuickAddIssueForm, + [EIssueLayoutTypes.CALENDAR]: CalendarQuickAddIssueForm, + [EIssueLayoutTypes.GANTT]: GanttQuickAddIssueForm, + [EIssueLayoutTypes.SPREADSHEET]: SpreadsheetQuickAddIssueForm, + }; + + const CurrentLayoutQuickAddIssueForm = QUICK_ADD_ISSUE_FORMS[layout] ?? null; + + if (!CurrentLayoutQuickAddIssueForm) return <>; + + return ( + + ); +}); diff --git a/web/core/components/issues/issue-layouts/calendar/index.ts b/web/core/components/issues/issue-layouts/calendar/index.ts index 527a9eff4..d6028f4f5 100644 --- a/web/core/components/issues/issue-layouts/calendar/index.ts +++ b/web/core/components/issues/issue-layouts/calendar/index.ts @@ -9,4 +9,4 @@ export * from "./issue-block-root"; export * from "./issue-block"; export * from "./week-days"; export * from "./week-header"; -export * from "./quick-add-issue-form"; +export * from "./quick-add-issue-actions"; diff --git a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx index 6be0a21e2..8860fcb6a 100644 --- a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react"; import { TIssue, TPaginationData } from "@plane/types"; // components -import { CalendarQuickAddIssueForm, CalendarIssueBlockRoot } from "@/components/issues"; +import { CalendarQuickAddIssueActions, CalendarIssueBlockRoot } from "@/components/issues"; // helpers import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { useIssuesStore } from "@/hooks/use-issue-layout-store"; @@ -75,9 +75,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { {enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
- ; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; + addIssuesToView?: (issueIds: string[]) => Promise; + onOpen?: () => void; +}; + +export const CalendarQuickAddIssueActions: FC = observer((props) => { + const { prePopulatedData, quickAddCallback, addIssuesToView, onOpen } = props; + // router + const { workspaceSlug, projectId, moduleId } = useParams(); + // states + const [isOpen, setIsOpen] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isExistingIssueModalOpen, setIsExistingIssueModalOpen] = useState(false); + const { updateIssue } = useIssueDetail(); + // derived values + const ExistingIssuesListModalPayload = addIssuesToView + ? moduleId + ? { module: moduleId.toString(), target_date: "none" } + : { cycle: true, target_date: "none" } + : { target_date: "none" }; + + const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; + + const issueIds = data.map((i) => i.id); + + try { + // To handle all updates in parallel + await Promise.all( + data.map((issue) => + updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, prePopulatedData ?? {}) + ) + ); + await addIssuesToView?.(issueIds); + } catch (error) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Something went wrong. Please try again.", + }); + } + }; + + const handleNewIssue = () => { + setIsOpen(true); + if (onOpen) onOpen(); + }; + const handleExistingIssue = () => { + setIsExistingIssueModalOpen(true); + }; + + if (!projectId) return null; + + return ( + <> + {workspaceSlug && projectId && ( + setIsExistingIssueModalOpen(false)} + searchParams={ExistingIssuesListModalPayload} + handleOnSubmit={handleAddIssuesToView} + shouldHideIssue={(issue) => { + if (issue.start_date && prePopulatedData?.target_date) { + const issueStartDate = new Date(issue.start_date); + const targetDate = new Date(prePopulatedData.target_date); + const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); + if (diffInDays < 0) return true; + } + return false; + }} + /> + )} + setIsOpen(isOpen)} + layout={EIssueLayoutTypes.CALENDAR} + prePopulatedData={prePopulatedData} + quickAddCallback={quickAddCallback} + customQuickAddButton={ +
+ setIsMenuOpen(true)} + onMenuClose={() => setIsMenuOpen(false)} + className="w-full" + customButtonClassName="w-full" + customButton={ +
+ + New issue +
+ } + > + New issue + Add existing issue +
+
+ } + /> + + ); +}); diff --git a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx deleted file mode 100644 index 4cc870be9..000000000 --- a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx +++ /dev/null @@ -1,264 +0,0 @@ -"use client"; - -import { useEffect, useRef, useState } from "react"; -import { differenceInCalendarDays } from "date-fns"; -import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { PlusIcon } from "lucide-react"; -// types -import { ISearchIssueResponse, TIssue } from "@plane/types"; -// ui -import { TOAST_TYPE, setPromiseToast, setToast, CustomMenu } from "@plane/ui"; -// components -import { ExistingIssuesListModal } from "@/components/core"; -import { CreateIssueToastActionItems } from "@/components/issues"; -// constants -import { ISSUE_CREATED } from "@/constants/event-tracker"; -// helpers -import { cn } from "@/helpers/common.helper"; -import { createIssuePayload } from "@/helpers/issue.helper"; -// hooks -import { useEventTracker, useIssueDetail, useProject } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; - -type Props = { - formKey: keyof TIssue; - groupId?: string; - subGroupId?: string | null; - prePopulatedData?: Partial; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; - addIssuesToView?: (issueIds: string[]) => Promise; - onOpen?: () => void; -}; - -const defaultValues: Partial = { - name: "", -}; - -const Inputs = (props: any) => { - const { register, setFocus, projectDetails } = props; - - useEffect(() => { - setFocus("name"); - }, [setFocus]); - - return ( - <> -

{projectDetails?.identifier ?? "..."}

- - - ); -}; - -export const CalendarQuickAddIssueForm: React.FC = observer((props) => { - const { formKey, prePopulatedData, quickAddCallback, addIssuesToView, onOpen } = props; - - // router - const { workspaceSlug, projectId, moduleId } = useParams(); - const pathname = usePathname(); - // store hooks - const { getProjectById } = useProject(); - const { captureIssueEvent } = useEventTracker(); - const { updateIssue } = useIssueDetail(); - // refs - const ref = useRef(null); - // states - const [isOpen, setIsOpen] = useState(false); - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [isExistingIssueModalOpen, setIsExistingIssueModalOpen] = useState(false); - // derived values - const projectDetail = projectId ? getProjectById(projectId.toString()) : null; - const ExistingIssuesListModalPayload = addIssuesToView - ? moduleId - ? { module: moduleId.toString(), target_date: "none" } - : { cycle: true, target_date: "none" } - : { target_date: "none" }; - - const { - reset, - handleSubmit, - register, - setFocus, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - const handleClose = () => { - setIsOpen(false); - }; - - useKeypress("Escape", handleClose); - useOutsideClickDetector(ref, handleClose); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - useEffect(() => { - if (!errors) return; - - Object.keys(errors).forEach((key) => { - const error = errors[key as keyof TIssue]; - - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error?.message?.toString() || "Some error occurred. Please try again.", - }); - }); - }, [errors]); - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !workspaceSlug || !projectId) return; - - reset({ ...defaultValues }); - - const payload = createIssuePayload(projectId.toString(), { - ...(prePopulatedData ?? {}), - ...formData, - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(projectId.toString(), { - ...payload, - }); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: "Calendar quick add" }, - path: pathname, - }); - }) - .catch(() => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: "Calendar quick add" }, - path: pathname, - }); - }); - } - }; - - const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { - if (!workspaceSlug || !projectId) return; - - const issueIds = data.map((i) => i.id); - - try { - // To handle all updates in parallel - await Promise.all( - data.map((issue) => - updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, prePopulatedData ?? {}) - ) - ); - await addIssuesToView?.(issueIds); - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong. Please try again.", - }); - } - }; - - const handleNewIssue = () => { - setIsOpen(true); - if (onOpen) onOpen(); - }; - const handleExistingIssue = () => { - setIsExistingIssueModalOpen(true); - }; - - return ( - <> - {workspaceSlug && projectId && ( - setIsExistingIssueModalOpen(false)} - searchParams={ExistingIssuesListModalPayload} - handleOnSubmit={handleAddIssuesToView} - shouldHideIssue={(issue) => { - if (issue.start_date && prePopulatedData?.target_date) { - const issueStartDate = new Date(issue.start_date); - const targetDate = new Date(prePopulatedData.target_date); - const diffInDays = differenceInCalendarDays(targetDate, issueStartDate); - if (diffInDays < 0) return true; - } - return false; - }} - /> - )} - {isOpen && ( -
-
- - -
- )} - - {!isOpen && ( -
- setIsMenuOpen(true)} - onMenuClose={() => setIsMenuOpen(false)} - className="w-full" - customButtonClassName="w-full" - customButton={ -
- - New issue -
- } - > - New issue - Add existing issue -
-
- )} - - ); -}); diff --git a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 82720a3be..d60373312 100644 --- a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -7,10 +7,12 @@ import { TIssue } from "@plane/types"; // hooks import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "@/components/gantt-chart"; import { getMonthChartItemPositionWidthInMonth } from "@/components/gantt-chart/views"; -import { GanttQuickAddIssueForm, IssueGanttBlock } from "@/components/issues"; +import { QuickAddIssueRoot, IssueGanttBlock, GanttQuickAddIssueButton } from "@/components/issues"; //constants import { EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue"; import { EUserProjectRoles } from "@/constants/project"; +// helpers +import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { getIssueBlocksStructure } from "@/helpers/issue.helper"; //hooks import { useIssues, useUser } from "@/hooks/store"; @@ -47,6 +49,9 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters; // plane web hooks const isBulkOperationsEnabled = useBulkOperationStatus(); + // derived values + const targetDate = new Date(); + targetDate.setDate(targetDate.getDate() + 1); useEffect(() => { fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }, viewId); @@ -89,7 +94,16 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan const quickAdd = enableIssueCreation && isAllowed && !isCompletedCycle ? ( - + ) : undefined; return ( diff --git a/web/core/components/issues/issue-layouts/gantt/index.ts b/web/core/components/issues/issue-layouts/gantt/index.ts index 5e741d30d..1cd0649fc 100644 --- a/web/core/components/issues/issue-layouts/gantt/index.ts +++ b/web/core/components/issues/issue-layouts/gantt/index.ts @@ -1,3 +1,3 @@ export * from "./blocks"; export * from "./base-gantt-root"; -export * from "./quick-add-issue-form"; + diff --git a/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx deleted file mode 100644 index f5996af69..000000000 --- a/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx +++ /dev/null @@ -1,179 +0,0 @@ -"use client"; - -import { useEffect, useState, useRef, FC } from "react"; -import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { PlusIcon } from "lucide-react"; -import { IProject, TIssue } from "@plane/types"; -// hooks -import { setPromiseToast } from "@plane/ui"; -import { CreateIssueToastActionItems } from "@/components/issues"; -import { ISSUE_CREATED } from "@/constants/event-tracker"; -import { cn } from "@/helpers/common.helper"; -import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; -import { createIssuePayload } from "@/helpers/issue.helper"; -import { useEventTracker, useProject } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -// helpers -// ui -// types -// constants - -interface IInputProps { - formKey: string; - register: any; - setFocus: any; - projectDetail: IProject | null; -} -const Inputs: FC = (props) => { - const { formKey, register, setFocus, projectDetail } = props; - - useEffect(() => { - setFocus(formKey); - }, [formKey, setFocus]); - - return ( -
-
{projectDetail?.identifier ?? "..."}
- -
- ); -}; - -type IGanttQuickAddIssueForm = { - prePopulatedData?: Partial; - onSuccess?: (data: TIssue) => Promise | void; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; -}; - -const defaultValues: Partial = { - name: "", -}; - -export const GanttQuickAddIssueForm: React.FC = observer((props) => { - const { prePopulatedData, quickAddCallback } = props; - // router - const { workspaceSlug, projectId } = useParams(); - const pathname = usePathname(); - // hooks - const { getProjectById } = useProject(); - const { captureIssueEvent } = useEventTracker(); - - const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined; - - const ref = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const handleClose = () => setIsOpen(false); - - useKeypress("Escape", handleClose); - useOutsideClickDetector(ref, handleClose); - - // form info - const { - reset, - handleSubmit, - setFocus, - register, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !workspaceSlug || !projectId) return; - - reset({ ...defaultValues }); - - const targetDate = new Date(); - targetDate.setDate(targetDate.getDate() + 1); - - const payload = createIssuePayload(projectId.toString(), { - ...(prePopulatedData ?? {}), - ...formData, - start_date: renderFormattedPayloadDate(new Date()), - target_date: renderFormattedPayloadDate(targetDate), - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: "Gantt quick add" }, - path: pathname, - }); - }) - .catch(() => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: "Gantt quick add" }, - path: pathname, - }); - }); - } - }; - return ( - <> - {isOpen ? ( -
-
-
- - -
{`Press 'Enter' to add another issue`}
-
-
- ) : ( - - )} - - ); -}); diff --git a/web/core/components/issues/issue-layouts/index.ts b/web/core/components/issues/issue-layouts/index.ts index fc294b084..7877eeaf5 100644 --- a/web/core/components/issues/issue-layouts/index.ts +++ b/web/core/components/issues/issue-layouts/index.ts @@ -18,3 +18,7 @@ export * from "./properties"; // save view export * from "./save-filter-view"; + +// quick add +export * from "./quick-add"; + diff --git a/web/core/components/issues/issue-layouts/kanban/index.ts b/web/core/components/issues/issue-layouts/kanban/index.ts index d0a391163..f84f7c8af 100644 --- a/web/core/components/issues/issue-layouts/kanban/index.ts +++ b/web/core/components/issues/issue-layouts/kanban/index.ts @@ -1,4 +1,3 @@ export * from "./block"; export * from "./roots"; export * from "./blocks-list"; -export * from "./quick-add-issue-form"; diff --git a/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx index d272bb949..0d680ad9c 100644 --- a/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/core/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -16,9 +16,11 @@ import { TIssueOrderByOptions, } from "@plane/types"; import { TOAST_TYPE, setToast } from "@plane/ui"; +import { KanbanQuickAddIssueButton, QuickAddIssueRoot } from "@/components/issues"; import { highlightIssueOnDrop } from "@/components/issues/issue-layouts/utils"; import { KanbanIssueBlockLoader } from "@/components/ui"; // helpers +import { EIssueLayoutTypes } from "@/constants/issue"; import { cn } from "@/helpers/common.helper"; // hooks import { useProjectState } from "@/hooks/store"; @@ -27,7 +29,7 @@ import { useIssuesStore } from "@/hooks/use-issue-layout-store"; import { GroupDragOverlay } from "../group-drag-overlay"; import { TRenderQuickActions } from "../list/list-view-types"; import { GroupDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload, getIssueBlockId } from "../utils"; -import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "."; +import { KanbanIssueBlocksList } from "."; interface IKanbanGroup { groupId: string; @@ -273,10 +275,9 @@ export const KanbanGroup = observer((props: IKanbanGroup) => { {enableQuickIssueCreate && !disableIssueCreation && (
- { - const { register, setFocus, projectDetail } = props; - - useEffect(() => { - setFocus("name"); - }, [setFocus]); - - return ( -
-

{projectDetail?.identifier ?? "..."}

- -
- ); -}; - -interface IKanBanQuickAddIssueForm { - formKey: keyof TIssue; - groupId?: string; - subGroupId?: string | null; - prePopulatedData?: Partial; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; -} - -const defaultValues: Partial = { - name: "", -}; - -export const KanBanQuickAddIssueForm: React.FC = observer((props) => { - const { formKey, prePopulatedData, quickAddCallback } = props; - // router - const { workspaceSlug, projectId } = useParams(); - const pathname = usePathname(); - // store hooks - const { getProjectById } = useProject(); - const { captureIssueEvent } = useEventTracker(); - - const projectDetail = projectId ? getProjectById(projectId.toString()) : null; - - const ref = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const handleClose = () => setIsOpen(false); - - useKeypress("Escape", handleClose); - useOutsideClickDetector(ref, handleClose); - - const { - reset, - handleSubmit, - setFocus, - register, - formState: { isSubmitting }, - } = useForm({ defaultValues }); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !workspaceSlug || !projectId) return; - - reset({ ...defaultValues }); - - const payload = createIssuePayload(projectId.toString(), { - ...(prePopulatedData ?? {}), - ...formData, - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(projectId.toString(), { - ...payload, - }); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: "Kanban quick add" }, - path: pathname, - }); - }) - .catch(() => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: "Kanban quick add" }, - path: pathname, - }); - }); - } - }; - - return ( - <> - {isOpen ? ( -
-
- - -
{`Press 'Enter' to add another issue`}
-
- ) : ( -
setIsOpen(true)} - > - - New Issue -
- )} - - ); -}); diff --git a/web/core/components/issues/issue-layouts/list/index.ts b/web/core/components/issues/issue-layouts/list/index.ts index 26c125b51..2171c1873 100644 --- a/web/core/components/issues/issue-layouts/list/index.ts +++ b/web/core/components/issues/issue-layouts/list/index.ts @@ -3,4 +3,3 @@ export * from "./block-root"; export * from "./block"; export * from "./roots"; export * from "./blocks-list"; -export * from "./quick-add-issue-form"; diff --git a/web/core/components/issues/issue-layouts/list/list-group.tsx b/web/core/components/issues/issue-layouts/list/list-group.tsx index 0f1b80694..1b5d88529 100644 --- a/web/core/components/issues/issue-layouts/list/list-group.tsx +++ b/web/core/components/issues/issue-layouts/list/list-group.tsx @@ -18,7 +18,7 @@ import { setToast, TOAST_TYPE } from "@plane/ui"; // components import { ListLoaderItemRow } from "@/components/ui"; // constants -import { DRAG_ALLOWED_GROUPS } from "@/constants/issue"; +import { DRAG_ALLOWED_GROUPS, EIssueLayoutTypes } from "@/constants/issue"; // hooks import { useProjectState } from "@/hooks/store"; import { useIntersectionObserver } from "@/hooks/use-intersection-observer"; @@ -26,6 +26,7 @@ import { useIssuesStore } from "@/hooks/use-issue-layout-store"; import { TSelectionHelper } from "@/hooks/use-multiple-select"; // components import { GroupDragOverlay } from "../group-drag-overlay"; +import { ListQuickAddIssueButton, QuickAddIssueRoot } from "../quick-add"; import { GroupDropLocation, getDestinationFromDropPayload, @@ -36,7 +37,6 @@ import { import { IssueBlocksList } from "./blocks-list"; import { HeaderGroupByCard } from "./headers/group-by-card"; import { TRenderQuickActions } from "./list-view-types"; -import { ListQuickAddIssueForm } from "./quick-add-issue-form"; interface Props { groupIssueIds: string[] | undefined; @@ -274,8 +274,11 @@ export const ListGroup = observer((props: Props) => { {enableIssueQuickAdd && !disableIssueCreation && !isGroupByCreatedBy && !isCompletedCycle && (
-
diff --git a/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx deleted file mode 100644 index 0025f2dfc..000000000 --- a/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx +++ /dev/null @@ -1,168 +0,0 @@ -"use client"; - -import { FC, useEffect, useState, useRef } from "react"; -import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { PlusIcon } from "lucide-react"; -import { TIssue, IProject } from "@plane/types"; -// hooks -import { setPromiseToast } from "@plane/ui"; -import { CreateIssueToastActionItems } from "@/components/issues"; -import { ISSUE_CREATED } from "@/constants/event-tracker"; -import { createIssuePayload } from "@/helpers/issue.helper"; -import { useEventTracker, useProject } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -// ui -// types -// helper -// constants - -interface IInputProps { - formKey: string; - register: any; - setFocus: any; - projectDetail: IProject | null; -} -const Inputs: FC = (props) => { - const { formKey, register, setFocus, projectDetail } = props; - - useEffect(() => { - setFocus(formKey); - }, [formKey, setFocus]); - - return ( -
-
{projectDetail?.identifier ?? "..."}
- -
- ); -}; - -interface IListQuickAddIssueForm { - prePopulatedData?: Partial; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; -} - -const defaultValues: Partial = { - name: "", -}; - -export const ListQuickAddIssueForm: FC = observer((props) => { - const { prePopulatedData, quickAddCallback } = props; - // router - const { workspaceSlug, projectId } = useParams(); - const pathname = usePathname(); - // hooks - const { getProjectById } = useProject(); - const { captureIssueEvent } = useEventTracker(); - - const projectDetail = (projectId && getProjectById(projectId.toString())) || undefined; - - const ref = useRef(null); - - const [isOpen, setIsOpen] = useState(false); - const handleClose = () => setIsOpen(false); - - useKeypress("Escape", handleClose); - useOutsideClickDetector(ref, handleClose); - - const { - reset, - handleSubmit, - setFocus, - register, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !workspaceSlug || !projectId) return; - - reset({ ...defaultValues }); - - const payload = createIssuePayload(projectId.toString(), { - ...(prePopulatedData ?? {}), - ...formData, - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: "List quick add" }, - path: pathname, - }); - }) - .catch(() => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: "List quick add" }, - path: pathname, - }); - }); - } - }; - - return ( -
- {isOpen ? ( -
-
- - -
{`Press 'Enter' to add another issue`}
-
- ) : ( -
setIsOpen(true)} - > - - New Issue -
- )} -
- ); -}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx new file mode 100644 index 000000000..41c5e12f9 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx @@ -0,0 +1,19 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { PlusIcon } from "lucide-react"; +import { TQuickAddIssueButton } from "../root"; + +export const GanttQuickAddIssueButton: FC = observer((props) => { + const { onClick } = props; + + return ( + + ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/index.ts b/web/core/components/issues/issue-layouts/quick-add/button/index.ts new file mode 100644 index 000000000..91477c053 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/button/index.ts @@ -0,0 +1,4 @@ +export * from "./list"; +export * from "./kanban"; +export * from "./gantt"; +export * from "./spreadsheet"; diff --git a/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx new file mode 100644 index 000000000..90411e74d --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx @@ -0,0 +1,18 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { PlusIcon } from "lucide-react"; +import { TQuickAddIssueButton } from "../root"; + +export const KanbanQuickAddIssueButton: FC = observer((props) => { + const { onClick } = props; + + return ( +
+ + New Issue +
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/list.tsx b/web/core/components/issues/issue-layouts/quick-add/button/list.tsx new file mode 100644 index 000000000..c5b77e1f0 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/button/list.tsx @@ -0,0 +1,18 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { PlusIcon } from "lucide-react"; +import { TQuickAddIssueButton } from "../root"; + +export const ListQuickAddIssueButton: FC = observer((props) => { + const { onClick } = props; + + return ( +
+ + New Issue +
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx new file mode 100644 index 000000000..b5663bb56 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx @@ -0,0 +1,21 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { PlusIcon } from "lucide-react"; +import { TQuickAddIssueButton } from "../root"; + +export const SpreadsheetAddIssueButton: FC = observer((props) => { + const { onClick } = props; + + return ( +
+ +
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx b/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx new file mode 100644 index 000000000..66e799056 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/calendar.tsx @@ -0,0 +1,31 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { TQuickAddIssueForm } from "../root"; + +export const CalendarQuickAddIssueForm: FC = observer((props) => { + const { ref, isOpen, projectDetail, register, onSubmit } = props; + + return ( +
+
+

{projectDetail?.identifier ?? "..."}

+ +
+
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx new file mode 100644 index 000000000..d0afadb09 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx @@ -0,0 +1,32 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { cn } from "@/helpers/common.helper"; +import { TQuickAddIssueForm } from "../root"; + +export const GanttQuickAddIssueForm: FC = observer((props) => { + const { ref, projectDetail, hasError, register, onSubmit } = props; + + return ( +
+
+
+
{projectDetail?.identifier ?? "..."}
+ +
+
+
{`Press 'Enter' to add another issue`}
+
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/index.ts b/web/core/components/issues/issue-layouts/quick-add/form/index.ts new file mode 100644 index 000000000..d0a45c124 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/index.ts @@ -0,0 +1,5 @@ +export * from "./list"; +export * from "./kanban"; +export * from "./gantt"; +export * from "./calendar"; +export * from "./spreadsheet"; diff --git a/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx new file mode 100644 index 000000000..8111f539e --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx @@ -0,0 +1,26 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { TQuickAddIssueForm } from "../root"; + +export const KanbanQuickAddIssueForm: FC = observer((props) => { + const { ref, projectDetail, register, onSubmit } = props; + + return ( +
+
+
+

{projectDetail?.identifier ?? "..."}

+ +
+
+
{`Press 'Enter' to add another issue`}
+
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/list.tsx b/web/core/components/issues/issue-layouts/quick-add/form/list.tsx new file mode 100644 index 000000000..0a894511f --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/list.tsx @@ -0,0 +1,31 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { TQuickAddIssueForm } from "../root"; + +export const ListQuickAddIssueForm: FC = observer((props) => { + const { ref, projectDetail, register, onSubmit } = props; + + return ( +
+
+
+
{projectDetail?.identifier ?? "..."}
+ +
+
+
{`Press 'Enter' to add another issue`}
+
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx new file mode 100644 index 000000000..b4317f359 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx @@ -0,0 +1,31 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +import { TQuickAddIssueForm } from "../root"; + +export const SpreadsheetQuickAddIssueForm: FC = observer((props) => { + const { ref, projectDetail, register, onSubmit } = props; + + return ( +
+
+

{projectDetail?.identifier ?? "..."}

+ +
+

+ Press {"'"}Enter{"'"} to add another issue +

+
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/quick-add/index.ts b/web/core/components/issues/issue-layouts/quick-add/index.ts new file mode 100644 index 000000000..a82947248 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/index.ts @@ -0,0 +1,3 @@ +export * from "./root"; +export * from "./form"; +export * from "./button"; diff --git a/web/core/components/issues/issue-layouts/quick-add/root.tsx b/web/core/components/issues/issue-layouts/quick-add/root.tsx new file mode 100644 index 000000000..4cdf2e8c0 --- /dev/null +++ b/web/core/components/issues/issue-layouts/quick-add/root.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { FC, useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import { useParams, usePathname } from "next/navigation"; +import { useForm, UseFormRegister } from "react-hook-form"; +import { PlusIcon } from "lucide-react"; +// types +import { IProject, TIssue } from "@plane/types"; +// ui +import { setPromiseToast } from "@plane/ui"; +// components +import { CreateIssueToastActionItems } from "@/components/issues"; +// constants +import { ISSUE_CREATED } from "@/constants/event-tracker"; +import { EIssueLayoutTypes } from "@/constants/issue"; +// helpers +import { cn } from "@/helpers/common.helper"; +import { createIssuePayload } from "@/helpers/issue.helper"; +// hooks +import { useEventTracker } from "@/hooks/store"; +// plane web components +import { QuickAddIssueFormRoot } from "@/plane-web/components/issues"; + +export type TQuickAddIssueForm = { + ref: React.RefObject; + isOpen: boolean; + projectDetail: IProject; + hasError: boolean; + register: UseFormRegister; + onSubmit: () => void; +}; + +export type TQuickAddIssueButton = { + onClick: () => void; +}; + +type TQuickAddIssueRoot = { + isQuickAddOpen?: boolean; + layout: EIssueLayoutTypes; + prePopulatedData?: Partial; + QuickAddButton?: FC; + customQuickAddButton?: JSX.Element; + containerClassName?: string; + setIsQuickAddOpen?: (isOpen: boolean) => void; + quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; +}; + +const defaultValues: Partial = { + name: "", +}; + +export const QuickAddIssueRoot: FC = observer((props) => { + const { + isQuickAddOpen, + layout, + prePopulatedData, + QuickAddButton, + customQuickAddButton, + containerClassName = "", + setIsQuickAddOpen, + quickAddCallback, + } = props; + // router + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + // states + const [isOpen, setIsOpen] = useState(isQuickAddOpen ?? false); + // store hooks + const { captureIssueEvent } = useEventTracker(); + // form info + const { + reset, + handleSubmit, + setFocus, + register, + formState: { errors, isSubmitting }, + } = useForm({ defaultValues }); + + useEffect(() => { + if (isQuickAddOpen !== undefined) { + setIsOpen(isQuickAddOpen); + } + }, [isQuickAddOpen]); + + useEffect(() => { + if (!isOpen) reset({ ...defaultValues }); + }, [isOpen, reset]); + + const handleIsOpen = (isOpen: boolean) => { + if (isQuickAddOpen !== undefined && setIsQuickAddOpen) { + setIsQuickAddOpen(isOpen); + } else { + setIsOpen(isOpen); + } + }; + + const onSubmitHandler = async (formData: TIssue) => { + if (isSubmitting || !workspaceSlug || !projectId) return; + + reset({ ...defaultValues }); + + const payload = createIssuePayload(projectId.toString(), { + ...(prePopulatedData ?? {}), + ...formData, + }); + + if (quickAddCallback) { + const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); + setPromiseToast(quickAddPromise, { + loading: "Adding issue...", + success: { + title: "Success!", + message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), + }, + error: { + title: "Error!", + message: (err) => err?.message || "Some error occurred. Please try again.", + }, + }); + + await quickAddPromise + .then((res) => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...res, state: "SUCCESS", element: ` ${layout} quick add` }, + path: pathname, + }); + }) + .catch(() => { + captureIssueEvent({ + eventName: ISSUE_CREATED, + payload: { ...payload, state: "FAILED", element: `${layout} quick ad` }, + path: pathname, + }); + }); + } + }; + + if (!projectId) return null; + + return ( +
+ {isOpen ? ( + handleIsOpen(false)} + /> + ) : ( + <> + {QuickAddButton && handleIsOpen(true)} />} + {customQuickAddButton && <>{customQuickAddButton}} + {!QuickAddButton && !customQuickAddButton && ( +
handleIsOpen(true)} + > + + New Issue +
+ )} + + )} +
+ ); +}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/index.ts b/web/core/components/issues/issue-layouts/spreadsheet/index.ts index 8fa49e851..75d40695f 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/index.ts +++ b/web/core/components/issues/issue-layouts/spreadsheet/index.ts @@ -1,5 +1,4 @@ export * from "./columns"; export * from "./roots"; export * from "./spreadsheet-view"; -export * from "./quick-add-issue-form"; export * from "./spreadsheet-header-column"; diff --git a/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx deleted file mode 100644 index e88887b43..000000000 --- a/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx +++ /dev/null @@ -1,238 +0,0 @@ -"use client"; - -import { useEffect, useState, useRef } from "react"; -import { observer } from "mobx-react"; -import { usePathname } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { PlusIcon } from "lucide-react"; -import { TIssue } from "@plane/types"; -// hooks -import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; -import { CreateIssueToastActionItems } from "@/components/issues"; -import { ISSUE_CREATED } from "@/constants/event-tracker"; -import { createIssuePayload } from "@/helpers/issue.helper"; -import { useEventTracker, useProject, useWorkspace } from "@/hooks/store"; -import useKeypress from "@/hooks/use-keypress"; -import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -// helpers -// ui -// types -// constants - -type Props = { - formKey: keyof TIssue; - groupId?: string; - subGroupId?: string | null; - prePopulatedData?: Partial; - quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise; -}; - -const defaultValues: Partial = { - name: "", -}; - -const Inputs = (props: any) => { - const { register, setFocus, projectDetails } = props; - - useEffect(() => { - setFocus("name"); - }, [setFocus]); - - return ( - <> -

{projectDetails?.identifier ?? "..."}

- - - ); -}; - -export const SpreadsheetQuickAddIssueForm: React.FC = observer((props) => { - const { formKey, prePopulatedData, quickAddCallback } = props; - // store hooks - const { currentWorkspace } = useWorkspace(); - const { currentProjectDetails } = useProject(); - const { captureIssueEvent } = useEventTracker(); - // router - const pathname = usePathname(); - // form info - const { - reset, - handleSubmit, - setFocus, - register, - formState: { errors, isSubmitting }, - } = useForm({ defaultValues }); - - // ref - const ref = useRef(null); - - // states - const [isOpen, setIsOpen] = useState(false); - - const handleClose = () => setIsOpen(false); - - // hooks - useKeypress("Escape", handleClose); - useOutsideClickDetector(ref, handleClose); - - useEffect(() => { - setFocus("name"); - }, [setFocus, isOpen]); - - useEffect(() => { - if (!isOpen) reset({ ...defaultValues }); - }, [isOpen, reset]); - - useEffect(() => { - if (!errors) return; - - Object.keys(errors).forEach((key) => { - const error = errors[key as keyof TIssue]; - - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error?.message?.toString() || "Some error occurred. Please try again.", - }); - }); - }, [errors]); - - // const onSubmitHandler = async (formData: TIssue) => { - // if (isSubmitting || !workspaceSlug || !projectId) return; - - // // resetting the form so that user can add another issue quickly - // reset({ ...defaultValues }); - - // const payload = createIssuePayload(workspaceDetail!, projectDetails!, { - // ...(prePopulatedData ?? {}), - // ...formData, - // }); - - // try { - // quickAddStore.createIssue( - // workspaceSlug.toString(), - // projectId.toString(), - // { - // group_id: groupId ?? null, - // sub_group_id: null, - // }, - // payload - // ); - - // setToast({ - // type: TOAST_TYPE.SUCCESS, - // title: "Success!", - // message: "Issue created successfully.", - // }); - // } catch (err: any) { - // Object.keys(err || {}).forEach((key) => { - // const error = err?.[key]; - // const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null; - - // setToast({ - // type: TOAST_TYPE.ERROR, - // title: "Error!", - // message: errorTitle || "Some error occurred. Please try again.", - // }); - // }); - // } - // }; - - const onSubmitHandler = async (formData: TIssue) => { - if (isSubmitting || !currentWorkspace || !currentProjectDetails) return; - - reset({ ...defaultValues }); - - const payload = createIssuePayload(currentProjectDetails.id, { - ...(prePopulatedData ?? {}), - ...formData, - }); - - if (quickAddCallback) { - const quickAddPromise = quickAddCallback(currentProjectDetails.id, { ...payload } as TIssue); - setPromiseToast(quickAddPromise, { - loading: "Adding issue...", - success: { - title: "Success!", - message: () => "Issue created successfully.", - actionItems: (data) => ( - - ), - }, - error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", - }, - }); - - await quickAddPromise - .then((res) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...res, state: "SUCCESS", element: "Spreadsheet quick add" }, - path: pathname, - }); - }) - .catch((err) => { - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { ...payload, state: "FAILED", element: "Spreadsheet quick add" }, - path: pathname, - }); - console.error(err); - }); - } - }; - - return ( -
- {isOpen && ( -
-
- - -
- )} - - {isOpen && ( -

- Press {"'"}Enter{"'"} to add another issue -

- )} - - {!isOpen && ( -
- -
- )} -
- ); -}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 32b8e287a..167aa46f4 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -5,8 +5,9 @@ import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@pl // components import { LogoSpinner } from "@/components/common"; import { MultipleSelectGroup } from "@/components/core"; -import { SpreadsheetQuickAddIssueForm } from "@/components/issues"; +import { QuickAddIssueRoot, SpreadsheetAddIssueButton } from "@/components/issues"; // constants +import { EIssueLayoutTypes } from "@/constants/issue"; import { SPREADSHEET_PROPERTY_LIST, SPREADSHEET_SELECT_GROUP } from "@/constants/spreadsheet"; // hooks import { useProject } from "@/hooks/store"; @@ -109,7 +110,11 @@ export const SpreadsheetView: React.FC = observer((props) => {
{enableQuickCreateIssue && !disableIssueCreation && ( - + )}
diff --git a/web/core/components/issues/title-input.tsx b/web/core/components/issues/title-input.tsx index a8edb792d..9bbd9bb2e 100644 --- a/web/core/components/issues/title-input.tsx +++ b/web/core/components/issues/title-input.tsx @@ -134,7 +134,7 @@ export const IssueTitleInput: FC = observer((props) => { /255
- {title?.length === 0 && Title is required} + {title?.length === 0 && Title is required} ); }); diff --git a/web/ee/components/issues/index.ts b/web/ee/components/issues/index.ts index 82d35e3aa..3bc3bdbfd 100644 --- a/web/ee/components/issues/index.ts +++ b/web/ee/components/issues/index.ts @@ -2,3 +2,4 @@ export * from "./bulk-operations"; export * from "./worklog"; export * from "./issue-modal"; export * from "./issue-details"; +export * from "./quick-add"; diff --git a/web/ee/components/issues/quick-add/index.ts b/web/ee/components/issues/quick-add/index.ts new file mode 100644 index 000000000..1efe34c51 --- /dev/null +++ b/web/ee/components/issues/quick-add/index.ts @@ -0,0 +1 @@ +export * from "./root"; diff --git a/web/ee/components/issues/quick-add/root.tsx b/web/ee/components/issues/quick-add/root.tsx new file mode 100644 index 000000000..87d9fd2e3 --- /dev/null +++ b/web/ee/components/issues/quick-add/root.tsx @@ -0,0 +1 @@ +export * from "ce/components/issues/quick-add/root";