diff --git a/.eslintrc.json b/apps/app/.eslintrc.json similarity index 100% rename from .eslintrc.json rename to apps/app/.eslintrc.json diff --git a/apps/app/components/command-palette/shortcuts.tsx b/apps/app/components/command-palette/shortcuts.tsx index dcba28c82..e4521b25a 100644 --- a/apps/app/components/command-palette/shortcuts.tsx +++ b/apps/app/components/command-palette/shortcuts.tsx @@ -1,15 +1,53 @@ -import React from "react"; +import React, { useState } from "react"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // icons import { XMarkIcon } from "@heroicons/react/20/solid"; +// ui +import { Input } from "ui"; type Props = { isOpen: boolean; setIsOpen: React.Dispatch>; }; +const shortcuts = [ + { + title: "Navigation", + shortcuts: [ + { keys: "ctrl,/", description: "To open navigator" }, + { keys: "↑", description: "Move up" }, + { keys: "↓", description: "Move down" }, + { keys: "←", description: "Move left" }, + { keys: "→", description: "Move right" }, + { keys: "Enter", description: "Select" }, + { keys: "Esc", description: "Close" }, + ], + }, + { + title: "Common", + shortcuts: [ + { keys: "ctrl,p", description: "To create project" }, + { keys: "ctrl,i", description: "To create issue" }, + { keys: "ctrl,q", description: "To create cycle" }, + { keys: "ctrl,h", description: "To open shortcuts guide" }, + { + keys: "ctrl,alt,c", + description: "To copy issue url when on issue detail page.", + }, + ], + }, +]; + const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => { + const [query, setQuery] = useState(""); + + const filteredShortcuts = shortcuts.filter((shortcut) => + shortcut.shortcuts.some((item) => item.description.includes(query.trim())) || query === "" + ? true + : false + ); + return ( @@ -39,7 +77,7 @@ const ShortcutsModal: React.FC = ({ isOpen, setIsOpen }) => {
-
+
= ({ isOpen, setIsOpen }) => { -
- {[ - { - title: "Navigation", - shortcuts: [ - { keys: "ctrl,/", description: "To open navigator" }, - { keys: "↑", description: "Move up" }, - { keys: "↓", description: "Move down" }, - { keys: "←", description: "Move left" }, - { keys: "→", description: "Move right" }, - { keys: "Enter", description: "Select" }, - { keys: "Esc", description: "Close" }, - ], - }, - { - title: "Common", - shortcuts: [ - { keys: "ctrl,p", description: "To create project" }, - { keys: "ctrl,i", description: "To create issue" }, - { keys: "ctrl,q", description: "To create cycle" }, - { keys: "ctrl,m", description: "To create module" }, - { keys: "ctrl,h", description: "To open shortcuts guide" }, - { - keys: "ctrl,alt,c", - description: "To copy issue url when on issue detail page.", - }, - ], - }, - ].map(({ title, shortcuts }) => ( -
-

{title}

-
- {shortcuts.map(({ keys, description }, index) => ( -
-

{description}

-
- {keys.split(",").map((key, index) => ( - - - {key} - - {/* {index !== keys.split(",").length - 1 ? ( - + - ) : null} */} - - ))} +
+ setQuery(e.target.value)} + /> +
+
+ {filteredShortcuts.length > 0 ? ( + filteredShortcuts.map(({ title, shortcuts }) => ( +
+

{title}

+
+ {shortcuts.map(({ keys, description }, index) => ( +
+

{description}

+
+ {keys.split(",").map((key, index) => ( + + + {key} + + + ))} +
-
- ))} + ))} +
+ )) + ) : ( +
+

+ No shortcuts found for{" "} + + {`"`} + {query} + {`"`} + +

- ))} + )}
diff --git a/apps/app/components/project/common/board-view/single-issue.tsx b/apps/app/components/project/common/board-view/single-issue.tsx new file mode 100644 index 000000000..a1c7b6ab3 --- /dev/null +++ b/apps/app/components/project/common/board-view/single-issue.tsx @@ -0,0 +1,388 @@ +// next +import Link from "next/link"; +import Image from "next/image"; +// react-beautiful-dnd +import { DraggableStateSnapshot } from "react-beautiful-dnd"; +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// icons +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CalendarDaysIcon } from "@heroicons/react/20/solid"; +import User from "public/user.png"; +// types +import { IIssue, IWorkspaceMember, Properties } from "types"; +// common +import { + addSpaceIfCamelCase, + classNames, + findHowManyDaysLeft, + renderShortNumericDateFormat, +} from "constants/common"; +// constants +import { PRIORITIES } from "constants/"; +import useUser from "lib/hooks/useUser"; +import React from "react"; + +type Props = { + issue: IIssue; + properties: Properties; + snapshot?: DraggableStateSnapshot; + assignees: { + avatar: string | undefined; + first_name: string | undefined; + email: string | undefined; + }[]; + people: IWorkspaceMember[] | undefined; + handleDeleteIssue?: React.Dispatch>; + partialUpdateIssue: (formData: Partial, childIssueId: string) => void; +}; + +const SingleIssue: React.FC = ({ + issue, + properties, + snapshot, + assignees, + people, + handleDeleteIssue, + partialUpdateIssue, +}) => { + const { activeProject, states } = useUser(); + + return ( +
+
+ {handleDeleteIssue && ( +
+ +
+ )} + + + {properties.key && ( +
+ {activeProject?.identifier}-{issue.sequence_id} +
+ )} +
+ {issue.name} +
+
+ +
+ {properties.priority && ( + { + partialUpdateIssue({ priority: data }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ + {issue.priority ?? "None"} + + + + + {PRIORITIES?.map((priority) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer capitalize select-none px-3 py-2" + ) + } + value={priority} + > + {priority} + + ))} + + +
+
+
Priority
+
+ {issue.priority ?? "None"} +
+
+ + )} +
+ )} + {properties.state && ( + { + partialUpdateIssue({ state: data }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ + + {addSpaceIfCamelCase(issue.state_detail.name)} + + + + + {states?.map((state) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none px-3 py-2" + ) + } + value={state.id} + > + {addSpaceIfCamelCase(state.name)} + + ))} + + +
+
+
State
+
{issue.state_detail.name}
+
+ + )} +
+ )} + {properties.start_date && ( +
+ + {issue.start_date ? renderShortNumericDateFormat(issue.start_date) : "N/A"} +
+
Started at
+
{renderShortNumericDateFormat(issue.start_date ?? "")}
+
+
+ )} + {properties.target_date && ( +
+ + {issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"} +
+
Target date
+
{renderShortNumericDateFormat(issue.target_date ?? "")}
+
+ {issue.target_date && + (issue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft(issue.target_date)} days` + : findHowManyDaysLeft(issue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft(issue.target_date)} days` + : "Target date")} +
+
+
+ )} + {properties.assignee && ( + { + const newData = issue.assignees ?? []; + if (newData.includes(data)) { + newData.splice(newData.indexOf(data), 1); + } else { + newData.push(data); + } + partialUpdateIssue({ assignees_list: newData }, issue.id); + }} + className="group relative flex-shrink-0" + > + {({ open }) => ( + <> +
+ +
+ {assignees.length > 0 ? ( + assignees.map((assignee, index: number) => ( +
+ {assignee.avatar && assignee.avatar !== "" ? ( +
+ {assignee?.first_name} +
+ ) : ( +
+ {assignee.first_name && assignee.first_name !== "" + ? assignee.first_name.charAt(0) + : assignee?.email?.charAt(0)} +
+ )} +
+ )) + ) : ( +
+ No user +
+ )} +
+
+ + + + {people?.map((person) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none p-2" + ) + } + value={person.member.id} + > +
+ {person.member.avatar && person.member.avatar !== "" ? ( +
+ avatar +
+ ) : ( +
+ {person.member.first_name && person.member.first_name !== "" + ? person.member.first_name.charAt(0) + : person.member.email.charAt(0)} +
+ )} +

+ {person.member.first_name && person.member.first_name !== "" + ? person.member.first_name + : person.member.email} +

+
+
+ ))} +
+
+
+
+
Assigned to
+
+ {issue.assignee_details?.length > 0 + ? issue.assignee_details.map((assignee) => assignee.first_name).join(", ") + : "No one"} +
+
+ + )} +
+ )} +
+
+
+ ); +}; + +export default SingleIssue; diff --git a/apps/app/components/project/cycles/board-view/index.tsx b/apps/app/components/project/cycles/board-view/index.tsx index 97ecd8290..d051ef064 100644 --- a/apps/app/components/project/cycles/board-view/index.tsx +++ b/apps/app/components/project/cycles/board-view/index.tsx @@ -16,6 +16,8 @@ type Props = { openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openIssuesListModal: () => void; removeIssueFromCycle: (bridgeId: string) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; + handleDeleteIssue: React.Dispatch>; }; const CyclesBoardView: React.FC = ({ @@ -26,6 +28,8 @@ const CyclesBoardView: React.FC = ({ openCreateIssueModal, openIssuesListModal, removeIssueFromCycle, + partialUpdateIssue, + handleDeleteIssue, }) => { const { states } = useUser(); @@ -57,6 +61,8 @@ const CyclesBoardView: React.FC = ({ removeIssueFromCycle={removeIssueFromCycle} openIssuesListModal={openIssuesListModal} openCreateIssueModal={openCreateIssueModal} + partialUpdateIssue={partialUpdateIssue} + handleDeleteIssue={handleDeleteIssue} /> ))}
diff --git a/apps/app/components/project/cycles/board-view/single-board.tsx b/apps/app/components/project/cycles/board-view/single-board.tsx index e8f44d8d1..2ba9bfe50 100644 --- a/apps/app/components/project/cycles/board-view/single-board.tsx +++ b/apps/app/components/project/cycles/board-view/single-board.tsx @@ -1,29 +1,25 @@ // react import React, { useState } from "react"; -// next -import Link from "next/link"; -import Image from "next/image"; // swr import useSWR from "swr"; +// services +import workspaceService from "lib/services/workspace.service"; // hooks import useUser from "lib/hooks/useUser"; +// components +import SingleIssue from "components/project/common/board-view/single-issue"; +// headless ui +import { Menu, Transition } from "@headlessui/react"; // ui import { CustomMenu } from "ui"; // icons -import { CalendarDaysIcon, PlusIcon } from "@heroicons/react/24/outline"; -import User from "public/user.png"; +import { PlusIcon } from "@heroicons/react/24/outline"; // types import { IIssue, IWorkspaceMember, NestedKeyOf, Properties } from "types"; -// constants +// fetch-keys import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; -import { - addSpaceIfCamelCase, - classNames, - findHowManyDaysLeft, - renderShortNumericDateFormat, -} from "constants/common"; -import workspaceService from "lib/services/workspace.service"; -import { Menu, Transition } from "@headlessui/react"; +// common +import { addSpaceIfCamelCase, classNames } from "constants/common"; type Props = { properties: Properties; @@ -37,6 +33,8 @@ type Props = { openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openIssuesListModal: () => void; removeIssueFromCycle: (bridgeId: string) => void; + partialUpdateIssue: (formData: Partial, issueId: string) => void; + handleDeleteIssue: React.Dispatch>; }; const SingleCycleBoard: React.FC = ({ @@ -49,11 +47,13 @@ const SingleCycleBoard: React.FC = ({ openCreateIssueModal, openIssuesListModal, removeIssueFromCycle, + partialUpdateIssue, + handleDeleteIssue, }) => { // Collapse/Expand const [show, setState] = useState(true); - const { activeWorkspace, activeProject } = useUser(); + const { activeWorkspace } = useUser(); if (selectedGroup === "priority") groupTitle === "high" @@ -138,172 +138,15 @@ const SingleCycleBoard: React.FC = ({ }); return ( -
-
- - - {properties.key && ( -
- {activeProject?.identifier}-{childIssue.sequence_id} -
- )} -
- {childIssue.name} -
-
- -
- {properties.priority && ( -
- {/* {getPriorityIcon(childIssue.priority ?? "")} */} - {childIssue.priority ?? "None"} -
-
Priority
-
- {childIssue.priority ?? "None"} -
-
-
- )} - {properties.state && ( -
- - {addSpaceIfCamelCase(childIssue.state_detail.name)} -
-
State
-
{childIssue.state_detail.name}
-
-
- )} - {properties.start_date && ( -
- - {childIssue.start_date - ? renderShortNumericDateFormat(childIssue.start_date) - : "N/A"} -
-
Started at
-
{renderShortNumericDateFormat(childIssue.start_date ?? "")}
-
-
- )} - {properties.target_date && ( -
- - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "N/A"} -
-
Target date
-
{renderShortNumericDateFormat(childIssue.target_date ?? "")}
-
- {childIssue.target_date && - (childIssue.target_date < new Date().toISOString() - ? `Target date has passed by ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : findHowManyDaysLeft(childIssue.target_date) <= 3 - ? `Target date is in ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : "Target date")} -
-
-
- )} - {properties.assignee && ( -
- {childIssue.assignee_details?.length > 0 ? ( - childIssue.assignee_details?.map((assignee, index: number) => ( -
- {assignee.avatar && assignee.avatar !== "" ? ( -
- {assignee.name} -
- ) : ( -
- {assignee.first_name.charAt(0)} -
- )} -
- )) - ) : ( -
- No user -
- )} -
-
Assigned to
-
- {childIssue.assignee_details?.length > 0 - ? childIssue.assignee_details - .map((assignee) => assignee.first_name) - .join(", ") - : "No one"} -
-
-
- )} -
-
-
+ ); })} diff --git a/apps/app/components/project/issues/BoardView/single-board.tsx b/apps/app/components/project/issues/BoardView/single-board.tsx index 8a30e330d..f9f579618 100644 --- a/apps/app/components/project/issues/BoardView/single-board.tsx +++ b/apps/app/components/project/issues/BoardView/single-board.tsx @@ -1,8 +1,5 @@ // react import React, { useState } from "react"; -// next -import Link from "next/link"; -import Image from "next/image"; // swr import useSWR from "swr"; // react-beautiful-dnd @@ -12,30 +9,18 @@ import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import workspaceService from "lib/services/workspace.service"; // hooks import useUser from "lib/hooks/useUser"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; // icons import { ArrowsPointingInIcon, ArrowsPointingOutIcon, - CalendarDaysIcon, EllipsisHorizontalIcon, PlusIcon, - TrashIcon, } from "@heroicons/react/24/outline"; -import User from "public/user.png"; -// common -import { PRIORITIES } from "constants/"; -import { - addSpaceIfCamelCase, - classNames, - findHowManyDaysLeft, - renderShortNumericDateFormat, -} from "constants/common"; +import { addSpaceIfCamelCase } from "constants/common"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; -import { getPriorityIcon } from "constants/global"; // types import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types"; +import SingleIssue from "components/project/common/board-view/single-issue"; type Props = { selectedGroup: NestedKeyOf | null; @@ -78,7 +63,7 @@ const SingleBoard: React.FC = ({ // Collapse/Expand const [show, setShow] = useState(true); - const { activeProject, activeWorkspace, states } = useUser(); + const { activeWorkspace } = useUser(); if (selectedGroup === "priority") groupTitle === "high" @@ -206,367 +191,19 @@ const SingleBoard: React.FC = ({ {(provided, snapshot) => (
-
-
- -
- - - {properties.key && ( -
- {activeProject?.identifier}-{childIssue.sequence_id} -
- )} -
- {childIssue.name} -
-
- -
- {properties.priority && ( - { - partialUpdateIssue({ priority: data }, childIssue.id); - }} - className="group relative flex-shrink-0" - > - {({ open }) => ( - <> -
- - {childIssue.priority ?? "None"} - - - - - {PRIORITIES?.map((priority) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer capitalize select-none px-3 py-2" - ) - } - value={priority} - > - {priority} - - ))} - - -
-
-
- Priority -
-
- {childIssue.priority ?? "None"} -
-
- - )} -
- )} - {properties.state && ( - { - partialUpdateIssue({ state: data }, childIssue.id); - }} - className="group relative flex-shrink-0" - > - {({ open }) => ( - <> -
- - - {addSpaceIfCamelCase(childIssue.state_detail.name)} - - - - - {states?.map((state) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer select-none px-3 py-2" - ) - } - value={state.id} - > - {addSpaceIfCamelCase(state.name)} - - ))} - - -
-
-
State
-
{childIssue.state_detail.name}
-
- - )} -
- )} - {properties.start_date && ( -
- - {childIssue.start_date - ? renderShortNumericDateFormat(childIssue.start_date) - : "N/A"} -
-
Started at
-
- {renderShortNumericDateFormat(childIssue.start_date ?? "")} -
-
-
- )} - {properties.target_date && ( -
- - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "N/A"} -
-
- Target date -
-
- {renderShortNumericDateFormat(childIssue.target_date ?? "")} -
-
- {childIssue.target_date && - (childIssue.target_date < new Date().toISOString() - ? `Target date has passed by ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : findHowManyDaysLeft(childIssue.target_date) <= 3 - ? `Target date is in ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : "Target date")} -
-
-
- )} - {properties.assignee && ( - { - const newData = childIssue.assignees ?? []; - if (newData.includes(data)) { - newData.splice(newData.indexOf(data), 1); - } else { - newData.push(data); - } - partialUpdateIssue( - { assignees_list: newData }, - childIssue.id - ); - }} - className="group relative flex-shrink-0" - > - {({ open }) => ( - <> -
- -
- {assignees.length > 0 ? ( - assignees.map((assignee, index: number) => ( -
- {assignee.avatar && assignee.avatar !== "" ? ( -
- {assignee?.first_name} -
- ) : ( -
- {assignee.first_name?.charAt(0)} -
- )} -
- )) - ) : ( -
- No user -
- )} -
-
- - - - {people?.map((person) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer select-none p-2" - ) - } - value={person.member.id} - > -
- {person.member.avatar && - person.member.avatar !== "" ? ( -
- avatar -
- ) : ( -
- {person.member.first_name && - person.member.first_name !== "" - ? person.member.first_name.charAt(0) - : person.member.email.charAt(0)} -
- )} -

- {person.member.first_name && - person.member.first_name !== "" - ? person.member.first_name - : person.member.email} -

-
-
- ))} -
-
-
-
-
Assigned to
-
- {childIssue.assignee_details?.length > 0 - ? childIssue.assignee_details - .map((assignee) => assignee.first_name) - .join(", ") - : "No one"} -
-
- - )} -
- )} -
-
+
)}
diff --git a/apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx similarity index 82% rename from apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx rename to apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx index 448c50acc..8b884788c 100644 --- a/apps/app/components/project/issues/BoardView/state/ConfirmStateDeletion.tsx +++ b/apps/app/components/project/issues/BoardView/state/confirm-state-delete.tsx @@ -9,6 +9,8 @@ import stateServices from "lib/services/state.service"; import { STATE_LIST } from "constants/fetch-keys"; // hooks import useUser from "lib/hooks/useUser"; +// common +import { groupBy } from "constants/common"; // icons import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui @@ -18,25 +20,27 @@ import { Button } from "ui"; import type { IState } from "types"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; - data?: IState; + onClose: () => void; + data: IState | null; }; -const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { +const ConfirmStateDeletion: React.FC = ({ isOpen, onClose, data }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const { activeWorkspace } = useUser(); + const [issuesWithThisStateExist, setIssuesWithThisStateExist] = useState(true); + + const { activeWorkspace, issues } = useUser(); const cancelButtonRef = useRef(null); const handleClose = () => { - setIsOpen(false); + onClose(); setIsDeleteLoading(false); }; const handleDeletion = async () => { setIsDeleteLoading(true); - if (!data || !activeWorkspace) return; + if (!data || !activeWorkspace || issuesWithThisStateExist) return; await stateServices .deleteState(activeWorkspace.slug, data.project, data.id) .then(() => { @@ -53,9 +57,11 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { }); }; + const groupedIssues = groupBy(issues?.results ?? [], "state"); + useEffect(() => { - data && setIsOpen(true); - }, [data, setIsOpen]); + if (data) setIssuesWithThisStateExist(!!groupedIssues[data.id]); + }, [groupedIssues, data]); return ( @@ -109,6 +115,14 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { This action cannot be undone.

+
+ {issuesWithThisStateExist && ( +

+ There are issues with this state. Please move them to another state + before deleting this state. +

+ )} +
@@ -117,7 +131,7 @@ const ConfirmStateDeletion: React.FC = ({ isOpen, setIsOpen, data }) => { type="button" onClick={handleDeletion} theme="danger" - disabled={isDeleteLoading} + disabled={isDeleteLoading || issuesWithThisStateExist} className="inline-flex sm:ml-3" > {isDeleteLoading ? "Deleting..." : "Delete"} diff --git a/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx new file mode 100644 index 000000000..10f186bd2 --- /dev/null +++ b/apps/app/components/project/issues/BoardView/state/create-update-state-inline.tsx @@ -0,0 +1,209 @@ +import React, { useEffect } from "react"; +// swr +import { mutate } from "swr"; +// react hook form +import { useForm, Controller } from "react-hook-form"; +// react color +import { TwitterPicker } from "react-color"; +// headless +import { Popover, Transition } from "@headlessui/react"; +// constants +import { GROUP_CHOICES } from "constants/"; +import { STATE_LIST } from "constants/fetch-keys"; +// services +import stateService from "lib/services/state.service"; +// ui +import { Button, Input, Select, Spinner } from "ui"; +// types +import type { IState } from "types"; + +type Props = { + workspaceSlug?: string; + projectId?: string; + data: IState | null; + onClose: () => void; + selectedGroup: StateGroup | null; +}; + +export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null; + +const defaultValues: Partial = { + name: "", + color: "#000000", + group: "backlog", +}; + +export const CreateUpdateStateInline: React.FC = ({ + workspaceSlug, + projectId, + data, + onClose, + selectedGroup, +}) => { + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + setError, + watch, + reset, + control, + } = useForm({ + defaultValues, + }); + + const handleClose = () => { + onClose(); + reset({ name: "", color: "#000000", group: "backlog" }); + }; + + const onSubmit = async (formData: IState) => { + if (!workspaceSlug || !projectId || isSubmitting) return; + const payload: IState = { + ...formData, + }; + if (!data) { + await stateService + .createState(workspaceSlug, projectId, { ...payload }) + .then((res) => { + mutate(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false); + handleClose(); + }) + .catch((err) => { + Object.keys(err).map((key) => { + setError(key as keyof IState, { + message: err[key].join(", "), + }); + }); + }); + } else { + await stateService + .updateState(workspaceSlug, projectId, data.id, { + ...payload, + }) + .then((res) => { + mutate( + STATE_LIST(projectId), + (prevData) => { + const newData = prevData?.map((item) => { + if (item.id === res.id) { + return res; + } + return item; + }); + return newData; + }, + false + ); + handleClose(); + }) + .catch((err) => { + Object.keys(err).map((key) => { + setError(key as keyof IState, { + message: err[key].join(", "), + }); + }); + }); + } + }; + + useEffect(() => { + if (data === null) return; + reset(data); + }, [data, reset]); + + useEffect(() => { + if (!data) + reset({ + ...defaultValues, + group: selectedGroup ?? "backlog", + }); + }, [selectedGroup, data, reset]); + + return ( +
+
+ + {({ open }) => ( + <> + + {watch("color") && watch("color") !== "" && ( + + )} + + + + + ( + onChange(value.hex)} /> + )} + /> + + + + )} + +
+ + {data && ( + + + +
+ ); +}; diff --git a/apps/app/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx b/apps/app/components/project/issues/BoardView/state/create-update-state-modal.tsx similarity index 91% rename from apps/app/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx rename to apps/app/components/project/issues/BoardView/state/create-update-state-modal.tsx index 3ba149820..4cd5a1367 100644 --- a/apps/app/components/project/issues/BoardView/state/CreateUpdateStateModal.tsx +++ b/apps/app/components/project/issues/BoardView/state/create-update-state-modal.tsx @@ -11,10 +11,12 @@ import { Dialog, Popover, Transition } from "@headlessui/react"; import stateService from "lib/services/state.service"; // fetch keys import { STATE_LIST } from "constants/fetch-keys"; +// constants +import { GROUP_CHOICES } from "constants/"; // hooks import useUser from "lib/hooks/useUser"; // ui -import { Button, Input, TextArea } from "ui"; +import { Button, Input, Select, TextArea } from "ui"; // icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; @@ -31,6 +33,7 @@ const defaultValues: Partial = { name: "", description: "", color: "#000000", + group: "backlog", }; const CreateUpdateStateModal: React.FC = ({ isOpen, data, projectId, handleClose }) => { @@ -161,6 +164,22 @@ const CreateUpdateStateModal: React.FC = ({ isOpen, data, projectId, hand }} />
+
+