diff --git a/apps/app/components/core/modals/link-modal.tsx b/apps/app/components/core/modals/link-modal.tsx index 543e733d6..b8d512a85 100644 --- a/apps/app/components/core/modals/link-modal.tsx +++ b/apps/app/components/core/modals/link-modal.tsx @@ -56,7 +56,7 @@ export const LinkModal: React.FC = ({ isOpen, handleClose, onFormSubmit } leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -70,7 +70,7 @@ export const LinkModal: React.FC = ({ isOpen, handleClose, onFormSubmit } leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
diff --git a/apps/app/components/core/sidebar/links-list.tsx b/apps/app/components/core/sidebar/links-list.tsx index 7094b9b86..a0619b924 100644 --- a/apps/app/components/core/sidebar/links-list.tsx +++ b/apps/app/components/core/sidebar/links-list.tsx @@ -49,7 +49,7 @@ export const LinksList: React.FC = ({ links, handleDeleteLink, userAuth } )}
diff --git a/apps/app/components/issues/sidebar-select/index.ts b/apps/app/components/issues/sidebar-select/index.ts index 1ec2c6ee0..5035325fd 100644 --- a/apps/app/components/issues/sidebar-select/index.ts +++ b/apps/app/components/issues/sidebar-select/index.ts @@ -2,8 +2,9 @@ export * from "./assignee"; export * from "./blocked"; export * from "./blocker"; export * from "./cycle"; +export * from "./estimate"; +export * from "./label"; export * from "./module"; export * from "./parent"; export * from "./priority"; export * from "./state"; -export * from "./estimate"; diff --git a/apps/app/components/issues/sidebar-select/label.tsx b/apps/app/components/issues/sidebar-select/label.tsx new file mode 100644 index 000000000..30ea71b04 --- /dev/null +++ b/apps/app/components/issues/sidebar-select/label.tsx @@ -0,0 +1,349 @@ +import React, { useEffect, useState } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// react-hook-form +import { Controller, UseFormWatch, useForm } from "react-hook-form"; +// react-color +import { TwitterPicker } from "react-color"; +// headless ui +import { Listbox, Popover, Transition } from "@headlessui/react"; +// services +import issuesService from "services/issues.service"; +// hooks +import useUser from "hooks/use-user"; +// ui +import { Input, Spinner } from "components/ui"; +// icons +import { + ChevronDownIcon, + PlusIcon, + RectangleGroupIcon, + TagIcon, + XMarkIcon, +} from "@heroicons/react/24/outline"; +// types +import { IIssue, IIssueLabels } from "types"; +// fetch-keys +import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; + +type Props = { + issueDetails: IIssue | undefined; + issueControl: any; + watchIssue: UseFormWatch; + submitChanges: (formData: any) => void; + isNotAllowed: boolean; + uneditable: boolean; +}; + +const defaultValues: Partial = { + name: "", + color: "#ff0000", +}; + +export const SidebarLabelSelect: React.FC = ({ + issueDetails, + issueControl, + watchIssue, + submitChanges, + isNotAllowed, + uneditable, +}) => { + const [createLabelForm, setCreateLabelForm] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + register, + handleSubmit, + formState: { isSubmitting }, + reset, + watch, + control: labelControl, + setFocus, + } = useForm>({ + defaultValues, + }); + + const { user } = useUser(); + + const { data: issueLabels, mutate: issueLabelMutate } = useSWR( + workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, + workspaceSlug && projectId + ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) + : null + ); + + const handleNewLabel = async (formData: Partial) => { + if (!workspaceSlug || !projectId || isSubmitting) return; + + await issuesService + .createIssueLabel(workspaceSlug as string, projectId as string, formData, user) + .then((res) => { + reset(defaultValues); + + issueLabelMutate((prevData) => [...(prevData ?? []), res], false); + + submitChanges({ labels_list: [...(issueDetails?.labels ?? []), res.id] }); + + setCreateLabelForm(false); + }); + }; + + useEffect(() => { + if (!createLabelForm) return; + + setFocus("name"); + reset(); + }, [createLabelForm, reset, setFocus]); + + return ( +
+
+
+ +

Label

+
+
+
+ {watchIssue("labels_list")?.map((labelId) => { + const label = issueLabels?.find((l) => l.id === labelId); + + if (label) + return ( + { + const updatedLabels = watchIssue("labels_list")?.filter((l) => l !== labelId); + submitChanges({ + labels_list: updatedLabels, + }); + }} + > + + {label.name} + + + ); + })} + ( + submitChanges({ labels_list: val })} + className="flex-shrink-0" + multiple + disabled={isNotAllowed || uneditable} + > + {({ open }) => ( +
+ + Select Label + + + + +
+ {issueLabels ? ( + issueLabels.length > 0 ? ( + issueLabels.map((label: IIssueLabels) => { + const children = issueLabels?.filter( + (l) => l.parent === label.id + ); + + if (children.length === 0) { + if (!label.parent) + return ( + + `${ + active || selected ? "bg-custom-background-90" : "" + } ${ + selected ? "" : "text-custom-text-200" + } flex cursor-pointer select-none items-center gap-2 truncate p-2` + } + value={label.id} + > + + {label.name} + + ); + } else + return ( +
+
+ + {label.name} +
+
+ {children.map((child) => ( + + `${ + active || selected + ? "bg-custom-background-100" + : "" + } ${ + selected ? "" : "text-custom-text-200" + } flex cursor-pointer select-none items-center gap-2 truncate p-2` + } + value={child.id} + > + + {child.name} + + ))} +
+
+ ); + }) + ) : ( +
No labels found
+ ) + ) : ( + + )} +
+
+
+
+ )} +
+ )} + /> + {!isNotAllowed && ( + + )} +
+
+
+ {createLabelForm && ( + +
+ + {({ open }) => ( + <> + + {watch("color") && watch("color") !== "" && ( + + )} + + + + + + ( + onChange(value.hex)} /> + )} + /> + + + + )} + +
+ + + + + )} +
+ ); +}; diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index c798e0759..24f66e803 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -1,15 +1,11 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useState } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import { mutate } from "swr"; // react-hook-form -import { useForm, Controller, UseFormWatch, Control } from "react-hook-form"; -// react-color -import { TwitterPicker } from "react-color"; -// headless ui -import { Popover, Listbox, Transition } from "@headlessui/react"; +import { Controller, UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; @@ -31,26 +27,24 @@ import { SidebarPrioritySelect, SidebarStateSelect, SidebarEstimateSelect, + SidebarLabelSelect, } from "components/issues"; // ui -import { Input, Spinner, CustomDatePicker } from "components/ui"; +import { CustomDatePicker } from "components/ui"; // icons import { - TagIcon, - ChevronDownIcon, LinkIcon, CalendarDaysIcon, TrashIcon, PlusIcon, XMarkIcon, - RectangleGroupIcon, } from "@heroicons/react/24/outline"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types -import type { ICycle, IIssue, IIssueLabels, IIssueLink, IModule } from "types"; +import type { ICycle, IIssue, IIssueLink, IModule } from "types"; // fetch-keys -import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; +import { ISSUE_DETAILS } from "constants/fetch-keys"; type Props = { control: any; @@ -76,11 +70,6 @@ type Props = { uneditable?: boolean; }; -const defaultValues: Partial = { - name: "", - color: "#ff0000", -}; - export const IssueDetailsSidebar: React.FC = ({ control, submitChanges, @@ -89,7 +78,6 @@ export const IssueDetailsSidebar: React.FC = ({ fieldsToShow = ["all"], uneditable = false, }) => { - const [createLabelForm, setCreateLabelForm] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [linkModal, setLinkModal] = useState(false); @@ -102,45 +90,6 @@ export const IssueDetailsSidebar: React.FC = ({ const { setToastAlert } = useToast(); - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) - : null - ); - - const { data: issueLabels, mutate: issueLabelMutate } = useSWR( - workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, - workspaceSlug && projectId - ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) - : null - ); - - const { - register, - handleSubmit, - formState: { isSubmitting }, - reset, - watch, - control: controlLabel, - } = useForm({ - defaultValues, - }); - - const handleNewLabel = (formData: any) => { - if (!workspaceSlug || !projectId || isSubmitting) return; - issuesService - .createIssueLabel(workspaceSlug as string, projectId as string, formData, user) - .then((res) => { - reset(defaultValues); - issueLabelMutate((prevData) => [...(prevData ?? []), res], false); - submitChanges({ labels_list: [...(issueDetail?.labels ?? []), res.id] }); - setCreateLabelForm(false); - }); - }; - const handleCycleChange = useCallback( (cycleDetails: ICycle) => { if (!workspaceSlug || !projectId || !issueDetail) return; @@ -243,12 +192,6 @@ export const IssueDetailsSidebar: React.FC = ({ }); }; - useEffect(() => { - if (!createLabelForm) return; - - reset(); - }, [createLabelForm, reset]); - const showFirstSection = fieldsToShow.includes("all") || fieldsToShow.includes("state") || @@ -283,7 +226,7 @@ export const IssueDetailsSidebar: React.FC = ({ data={issueDetail ?? null} user={user} /> -
+

{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id} @@ -310,445 +253,203 @@ export const IssueDetailsSidebar: React.FC = ({

-
- {showFirstSection && ( -
- {(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && ( - ( - submitChanges({ state: val })} - userAuth={memberRole} - disabled={uneditable} - /> - )} - /> - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && ( - ( - submitChanges({ assignees_list: val })} - userAuth={memberRole} - disabled={uneditable} - /> - )} - /> - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("priority")) && ( - ( - submitChanges({ priority: val })} - userAuth={memberRole} - disabled={uneditable} - /> - )} - /> - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && ( - ( - submitChanges({ estimate_point: val })} - userAuth={memberRole} - disabled={uneditable} - /> - )} - /> - )} -
- )} - {showSecondSection && ( -
- {(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && ( - submitChanges({ parent: null })} - > - Selected:{" "} - {issueDetail.parent_detail?.name} - - - ) : ( -
- No parent selected -
- ) - } - watch={watchIssue} - userAuth={memberRole} - disabled={uneditable} - /> - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && ( - - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( - - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && ( -
-
- -

Due date

+
+
+ {showFirstSection && ( +
+ {(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && ( + ( + submitChanges({ state: val })} + userAuth={memberRole} + disabled={uneditable} + /> + )} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && ( + ( + submitChanges({ assignees_list: val })} + userAuth={memberRole} + disabled={uneditable} + /> + )} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("priority")) && ( + ( + submitChanges({ priority: val })} + userAuth={memberRole} + disabled={uneditable} + /> + )} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && ( + ( + submitChanges({ estimate_point: val })} + userAuth={memberRole} + disabled={uneditable} + /> + )} + /> + )} +
+ )} + {showSecondSection && ( +
+ {(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && ( + submitChanges({ parent: null })} + > + Selected:{" "} + {issueDetail.parent_detail?.name} + + + ) : ( +
+ No parent selected +
+ ) + } + watch={watchIssue} + userAuth={memberRole} + disabled={uneditable} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && ( + + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( + + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && ( +
+
+ +

Due date

+
+
+ ( + + submitChanges({ + target_date: val, + }) + } + className="bg-custom-background-90" + disabled={isNotAllowed || uneditable} + /> + )} + /> +
-
- ( - - submitChanges({ - target_date: val, - }) - } - className="bg-custom-background-90" - disabled={isNotAllowed || uneditable} - /> - )} - /> -
-
- )} -
+ )} +
+ )} + {showThirdSection && ( +
+ {(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && ( + + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && ( + + )} +
+ )} +
+ {(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && ( + )} - {showThirdSection && ( -
- {(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && ( - - )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && ( - - )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && ( +
+
+

Links

+ {!isNotAllowed && ( + + )} +
+
+ {issueDetail?.issue_link && issueDetail.issue_link.length > 0 ? ( + + ) : null} +
)}
- {(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && ( -
-
-
- -

Label

-
-
-
- {watchIssue("labels_list")?.map((labelId) => { - const label = issueLabels?.find((l) => l.id === labelId); - - if (label) - return ( - { - const updatedLabels = watchIssue("labels_list")?.filter( - (l) => l !== labelId - ); - submitChanges({ - labels_list: updatedLabels, - }); - }} - > - - {label.name} - - - ); - })} - ( - submitChanges({ labels_list: val })} - className="flex-shrink-0" - multiple - disabled={isNotAllowed || uneditable} - > - {({ open }) => ( -
- - Select Label - - - - -
- {issueLabels ? ( - issueLabels.length > 0 ? ( - issueLabels.map((label: IIssueLabels) => { - const children = issueLabels?.filter( - (l) => l.parent === label.id - ); - - if (children.length === 0) { - if (!label.parent) - return ( - - `${ - active || selected - ? "bg-custom-background-90" - : "" - } ${ - selected ? "" : "text-custom-text-200" - } flex cursor-pointer select-none items-center gap-2 truncate p-2` - } - value={label.id} - > - - {label.name} - - ); - } else - return ( -
-
- - {label.name} -
-
- {children.map((child) => ( - - `${ - active || selected - ? "bg-custom-background-100" - : "" - } ${ - selected ? "" : "text-custom-text-200" - } flex cursor-pointer select-none items-center gap-2 truncate p-2` - } - value={child.id} - > - - {child.name} - - ))} -
-
- ); - }) - ) : ( -
No labels found
- ) - ) : ( - - )} -
-
-
-
- )} -
- )} - /> - {!isNotAllowed && ( - - )} -
-
-
- {createLabelForm && ( -
-
- - {({ open }) => ( - <> - - {watch("color") && watch("color") !== "" && ( - - )} - - - - - - ( - onChange(value.hex)} - /> - )} - /> - - - - )} - -
- - - -
- )} -
- )} - {(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && ( -
-
-

Links

- {!isNotAllowed && ( - - )} -
-
- {issueDetail?.issue_link && issueDetail.issue_link.length > 0 ? ( - - ) : null} -
-
- )}
); diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 267f185cb..9af7f7c7e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -119,11 +119,11 @@ const IssueDetailsPage: NextPage = () => { } > {issueDetails && projectId ? ( -
-
+
+
-
+