From 44bffd912767cdb0c9e65657f78cd231dba0ab21 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 14 Feb 2023 19:55:03 +0530 Subject: [PATCH] feat: create label option in create issue modal --- .../components/account/email-code-form.tsx | 8 +- .../components/core/board-view/all-boards.tsx | 6 + .../core/board-view/single-board.tsx | 9 + .../core/board-view/single-issue.tsx | 29 +- .../components/core/issues-view-filter.tsx | 37 ++- apps/app/components/core/issues-view.tsx | 8 + .../core/sidebar/sidebar-progress-stats.tsx | 7 +- apps/app/components/issues/form.tsx | 18 +- apps/app/components/issues/select/label.tsx | 91 +----- .../components/labels/create-label-modal.tsx | 189 +++++++++++++ apps/app/components/labels/index.ts | 1 + .../modules/delete-module-modal.tsx | 4 - apps/app/components/modules/sidebar.tsx | 18 +- .../components/modules/single-module-card.tsx | 209 +++++++------- .../components/states/create-state-modal.tsx | 227 +++++++++++++++ .../states/create-update-state-modal.tsx | 263 ------------------ apps/app/components/states/index.ts | 2 +- apps/app/contexts/issue-view.context.tsx | 3 +- .../[projectId]/modules/[moduleId].tsx | 62 +---- .../index.tsx => invitations.tsx} | 61 ++-- 20 files changed, 680 insertions(+), 572 deletions(-) create mode 100644 apps/app/components/labels/create-label-modal.tsx create mode 100644 apps/app/components/states/create-state-modal.tsx delete mode 100644 apps/app/components/states/create-update-state-modal.tsx rename apps/app/pages/{invitations/index.tsx => invitations.tsx} (81%) diff --git a/apps/app/components/account/email-code-form.tsx b/apps/app/components/account/email-code-form.tsx index 98ab10cb7b8..003d515f4f3 100644 --- a/apps/app/components/account/email-code-form.tsx +++ b/apps/app/components/account/email-code-form.tsx @@ -114,15 +114,15 @@ export const EmailCodeForm = ({ onSuccess }: any) => { error={errors.token} placeholder="Enter code" /> - {/* { - console.log("Triggered"); handleSubmit(onSubmit); }} > Resend code - */} + */} )}
diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index d4e995403c3..f5b03267ccf 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -11,9 +11,11 @@ type Props = { states: IState[] | undefined; members: IProjectMember[] | undefined; addIssueToState: (groupTitle: string, stateId: string | null) => void; + handleEditIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; handleTrashBox: (isDragging: boolean) => void; + removeIssue: ((bridgeId: string) => void) | null; userAuth: UserAuth; }; @@ -23,9 +25,11 @@ export const AllBoards: React.FC = ({ states, members, addIssueToState, + handleEditIssue, openIssuesListModal, handleDeleteIssue, handleTrashBox, + removeIssue, userAuth, }) => { const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues); @@ -57,11 +61,13 @@ export const AllBoards: React.FC = ({ groupedByIssues={groupedByIssues} selectedGroup={selectedGroup} members={members} + handleEditIssue={handleEditIssue} addIssueToState={() => addIssueToState(singleGroup, stateId)} handleDeleteIssue={handleDeleteIssue} openIssuesListModal={openIssuesListModal ?? null} orderBy={orderBy} handleTrashBox={handleTrashBox} + removeIssue={removeIssue} userAuth={userAuth} /> ); diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index e7cb49798ab..dfd012fac9a 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -25,11 +25,13 @@ type Props = { }; selectedGroup: NestedKeyOf | null; members: IProjectMember[] | undefined; + handleEditIssue: (issue: IIssue) => void; addIssueToState: () => void; handleDeleteIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; orderBy: NestedKeyOf | "manual" | null; handleTrashBox: (isDragging: boolean) => void; + removeIssue: ((bridgeId: string) => void) | null; userAuth: UserAuth; }; @@ -40,11 +42,13 @@ export const SingleBoard: React.FC = ({ groupedByIssues, selectedGroup, members, + handleEditIssue, addIssueToState, handleDeleteIssue, openIssuesListModal, orderBy, handleTrashBox, + removeIssue, userAuth, }) => { // collapse/expand @@ -106,10 +110,15 @@ export const SingleBoard: React.FC = ({ snapshot={snapshot} type={type} issue={issue} + selectedGroup={selectedGroup} properties={properties} + editIssue={() => handleEditIssue(issue)} handleDeleteIssue={handleDeleteIssue} orderBy={orderBy} handleTrashBox={handleTrashBox} + removeIssue={() => { + removeIssue && removeIssue(issue.bridge); + }} userAuth={userAuth} /> )} diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 945592788fb..a19c683e2be 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -23,6 +23,8 @@ import { ViewPrioritySelect, ViewStateSelect, } from "components/issues/view-select"; +// ui +import { CustomMenu } from "components/ui"; // types import { CycleIssueResponse, @@ -41,7 +43,10 @@ type Props = { provided: DraggableProvided; snapshot: DraggableStateSnapshot; issue: IIssue; + selectedGroup: NestedKeyOf | null; properties: Properties; + editIssue: () => void; + removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; orderBy: NestedKeyOf | "manual" | null; handleTrashBox: (isDragging: boolean) => void; @@ -53,7 +58,10 @@ export const SingleBoardIssue: React.FC = ({ provided, snapshot, issue, + selectedGroup, properties, + editIssue, + removeIssue, handleDeleteIssue, orderBy, handleTrashBox, @@ -170,13 +178,26 @@ export const SingleBoardIssue: React.FC = ({
{!isNotAllowed && (
- + */} + {type && !isNotAllowed && ( + + Edit + {type !== "issue" && removeIssue && ( + + <>Remove from {type} + + )} + handleDeleteIssue(issue)}> + Delete permanently + + + )}
)} @@ -195,7 +216,7 @@ export const SingleBoardIssue: React.FC = ({
- {properties.priority && ( + {properties.priority && selectedGroup !== "priority" && ( = ({ position="left" /> )} - {properties.state && ( + {properties.state && selectedGroup !== "state_detail.name" && ( = ({ issues }) => {

Display Properties

- {Object.keys(properties).map((key) => ( - - ))} + {Object.keys(properties).map((key) => { + if ( + issueView === "kanban" && + ((groupByProperty === "state_detail.name" && key === "state") || + (groupByProperty === "priority" && key === "priority")) + ) + return; + + return ( + + ); + })}
diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 2edb7f8043b..b3278ec3ef7 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -452,9 +452,17 @@ export const IssuesView: React.FC = ({ states={states} members={members} addIssueToState={addIssueToState} + handleEditIssue={handleEditIssue} openIssuesListModal={type !== "issue" ? openIssuesListModal : null} handleDeleteIssue={handleDeleteIssue} handleTrashBox={handleTrashBox} + removeIssue={ + type === "cycle" + ? removeIssueFromCycle + : type === "module" + ? removeIssueFromModule + : null + } userAuth={userAuth} /> )} diff --git a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx index ea8e1e401c1..8fb073f54d2 100644 --- a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx +++ b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx @@ -10,6 +10,8 @@ import { Tab } from "@headlessui/react"; // services import issuesServices from "services/issues.service"; import projectService from "services/project.service"; +// hooks +import useLocalStorage from "hooks/use-local-storage"; // components import { SingleProgressStats } from "components/core"; // ui @@ -20,7 +22,6 @@ import User from "public/user.png"; import { IIssue, IIssueLabels } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys"; -import useLocalStorage from "hooks/use-local-storage"; // types type Props = { groupedIssues: any; @@ -39,8 +40,10 @@ const stateGroupColours: { export const SidebarProgressStats: React.FC = ({ groupedIssues, issues }) => { const router = useRouter(); - const [tab, setTab] = useLocalStorage("tab", "Assignees"); const { workspaceSlug, projectId } = router.query; + + const [tab, setTab] = useLocalStorage("tab", "Assignees"); + const { data: issueLabels } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, workspaceSlug && projectId diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 5e034822aaa..3194481b6ec 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -16,8 +16,9 @@ import { IssueStateSelect, } from "components/issues/select"; import { CycleSelect as IssueCycleSelect } from "components/cycles/select"; -import { CreateUpdateStateModal } from "components/states"; +import { CreateStateModal } from "components/states"; import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal"; +import { CreateLabelModal } from "components/labels"; // ui import { Button, CustomDatePicker, CustomMenu, Input, Loader } from "components/ui"; // icons @@ -74,6 +75,7 @@ export const IssueForm: FC = ({ const [mostSimilarIssue, setMostSimilarIssue] = useState(); const [cycleModal, setCycleModal] = useState(false); const [stateModal, setStateModal] = useState(false); + const [labelModal, setLabelModal] = useState(false); const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); const router = useRouter(); @@ -121,7 +123,7 @@ export const IssueForm: FC = ({ <> {projectId && ( <> - setStateModal(false)} projectId={projectId} @@ -131,6 +133,11 @@ export const IssueForm: FC = ({ setIsOpen={setCycleModal} projectId={projectId} /> + setLabelModal(false)} + projectId={projectId} + /> )}
@@ -281,7 +288,12 @@ export const IssueForm: FC = ({ control={control} name="labels_list" render={({ field: { value, onChange } }) => ( - + )} />
diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index 2d4e5a179c3..f0a1d2d6438 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -1,15 +1,13 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; -// react-hook-form -import { useForm } from "react-hook-form"; // headless ui import { Combobox, Transition } from "@headlessui/react"; // icons -import { RectangleGroupIcon, TagIcon } from "@heroicons/react/24/outline"; +import { PlusIcon, RectangleGroupIcon, TagIcon } from "@heroicons/react/24/outline"; // services import issuesServices from "services/issues.service"; // types @@ -18,55 +16,26 @@ import type { IIssueLabels } from "types"; import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; type Props = { + setIsOpen: React.Dispatch>; value: string[]; onChange: (value: string[]) => void; projectId: string; }; -const defaultValues: Partial = { - name: "", -}; - -export const IssueLabelSelect: React.FC = ({ value, onChange, projectId }) => { +export const IssueLabelSelect: React.FC = ({ setIsOpen, value, onChange, projectId }) => { // states const [query, setQuery] = useState(""); const router = useRouter(); const { workspaceSlug } = router.query; - const [isOpen, setIsOpen] = useState(false); - - const { data: issueLabels, mutate: issueLabelsMutate } = useSWR( + const { data: issueLabels } = useSWR( projectId ? PROJECT_ISSUE_LABELS(projectId) : null, workspaceSlug && projectId ? () => issuesServices.getIssueLabels(workspaceSlug as string, projectId) : null ); - const onSubmit = async (data: IIssueLabels) => { - if (!projectId || !workspaceSlug || isSubmitting) return; - await issuesServices - .createIssueLabel(workspaceSlug as string, projectId as string, data) - .then((response) => { - issueLabelsMutate((prevData) => [...(prevData ?? []), response], false); - setIsOpen(false); - reset(defaultValues); - }) - .catch((error) => { - console.log(error); - }); - }; - - const { - formState: { isSubmitting }, - setFocus, - reset, - } = useForm({ defaultValues }); - - useEffect(() => { - isOpen && setFocus("name"); - }, [isOpen, setFocus]); - const filteredOptions = query === "" ? issueLabels @@ -175,48 +144,14 @@ export const IssueLabelSelect: React.FC = ({ value, onChange, projectId } ) : (

Loading...

)} - {/*
- {isOpen ? ( -
- - - -
- ) : ( - - )} -
*/} +
diff --git a/apps/app/components/labels/create-label-modal.tsx b/apps/app/components/labels/create-label-modal.tsx new file mode 100644 index 00000000000..89c2860122d --- /dev/null +++ b/apps/app/components/labels/create-label-modal.tsx @@ -0,0 +1,189 @@ +import React, { useEffect } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// react-hook-form +import { Controller, useForm } from "react-hook-form"; +// react-color +import { TwitterPicker } from "react-color"; +// headless ui +import { Dialog, Popover, Transition } from "@headlessui/react"; +// services +import issuesService from "services/issues.service"; +// ui +import { Button, Input } from "components/ui"; +// icons +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +// types +import type { IIssueLabels, IState } from "types"; +// constants +import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; + +// types +type Props = { + isOpen: boolean; + projectId: string; + handleClose: () => void; +}; + +const defaultValues: Partial = { + name: "", + color: "#000000", +}; + +export const CreateLabelModal: React.FC = ({ isOpen, projectId, handleClose }) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + watch, + control, + reset, + setError, + } = useForm({ + defaultValues, + }); + + const onClose = () => { + handleClose(); + reset(defaultValues); + }; + + const onSubmit = async (formData: IIssueLabels) => { + if (!workspaceSlug) return; + + await issuesService + .createIssueLabel(workspaceSlug as string, projectId as string, formData) + .then((res) => { + mutate( + PROJECT_ISSUE_LABELS(projectId), + (prevData) => [res, ...(prevData ?? [])], + false + ); + onClose(); + }) + .catch((error) => { + console.log(error); + }); + }; + + return ( + + + +
+ + +
+
+ + + +
+ + Create Label + +
+ + {({ open }) => ( + <> + + Color + {watch("color") && watch("color") !== "" && ( + + )} + + + + + ( + onChange(value.hex)} + /> + )} + /> + + + + )} + + +
+
+
+ + +
+ +
+
+
+
+
+
+ ); +}; diff --git a/apps/app/components/labels/index.ts b/apps/app/components/labels/index.ts index d407cd0741e..db02d29f02b 100644 --- a/apps/app/components/labels/index.ts +++ b/apps/app/components/labels/index.ts @@ -1,3 +1,4 @@ +export * from "./create-label-modal"; export * from "./create-update-label-inline"; export * from "./labels-list-modal"; export * from "./single-label-group"; diff --git a/apps/app/components/modules/delete-module-modal.tsx b/apps/app/components/modules/delete-module-modal.tsx index 317f49a6863..05ed6708943 100644 --- a/apps/app/components/modules/delete-module-modal.tsx +++ b/apps/app/components/modules/delete-module-modal.tsx @@ -65,10 +65,6 @@ export const DeleteModuleModal: React.FC = ({ isOpen, setIsOpen, data }) }); }; - useEffect(() => { - data && setIsOpen(true); - }, [data, setIsOpen]); - return ( void; }; -export const ModuleDetailsSidebar: React.FC = ({ - issues, - module, - isOpen, - moduleIssues, - handleDeleteModule, -}) => { +export const ModuleDetailsSidebar: React.FC = ({ issues, module, isOpen, moduleIssues }) => { + const [moduleDeleteModal, setModuleDeleteModal] = useState(false); const [moduleLinkModal, setModuleLinkModal] = useState(false); const router = useRouter(); @@ -127,6 +122,11 @@ export const ModuleDetailsSidebar: React.FC = ({ handleClose={() => setModuleLinkModal(false)} module={module} /> +
= ({ diff --git a/apps/app/components/modules/single-module-card.tsx b/apps/app/components/modules/single-module-card.tsx index 6bd59d14fe1..c8cca72e9b7 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -21,132 +21,131 @@ type Props = { }; export const SingleModuleCard: React.FC = ({ module }) => { + const [selectedModuleForDelete, setSelectedModuleForDelete] = useState(); + const [moduleDeleteModal, setModuleDeleteModal] = useState(false); + const router = useRouter(); const { workspaceSlug } = router.query; - const [moduleDeleteModal, setModuleDeleteModal] = useState(false); - const [selectedModuleForDelete, setSelectedModuleForDelete] = useState(); + const handleDeleteModule = () => { if (!module) return; - setSelectedModuleForDelete({ ...module, actionType: "delete" }); setModuleDeleteModal(true); }; return ( -
-
- -
+ <> - - - {module.name} -
-
-
LEAD
-
- {module.lead ? ( - module.lead_detail?.avatar && module.lead_detail.avatar !== "" ? ( -
+
+
+ +
+ +
+ {module.name} +
+
+
LEAD
+
+ {module.lead ? ( + module.lead_detail?.avatar && module.lead_detail.avatar !== "" ? ( +
+ {module.lead_detail.first_name} +
+ ) : ( +
+ {module.lead_detail?.first_name && module.lead_detail.first_name !== "" + ? module.lead_detail.first_name.charAt(0) + : module.lead_detail?.email.charAt(0)} +
+ ) + ) : ( + "N/A" + )} +
+
+
+
MEMBERS
+
+ {module.members && module.members.length > 0 ? ( + module?.members_detail?.map((member, index: number) => ( +
+ {member?.avatar && member.avatar !== "" ? ( +
+ {member?.first_name} +
+ ) : ( +
+ {member?.first_name && member.first_name !== "" + ? member.first_name.charAt(0) + : member?.email?.charAt(0)} +
+ )} +
+ )) + ) : ( +
{module.lead_detail.first_name}
- ) : ( -
- {module.lead_detail?.first_name && module.lead_detail.first_name !== "" - ? module.lead_detail.first_name.charAt(0) - : module.lead_detail?.email.charAt(0)} -
- ) - ) : ( - "N/A" - )} + )} +
-
-
+ + +
+ ); }; diff --git a/apps/app/components/states/create-state-modal.tsx b/apps/app/components/states/create-state-modal.tsx new file mode 100644 index 00000000000..5986b4e5338 --- /dev/null +++ b/apps/app/components/states/create-state-modal.tsx @@ -0,0 +1,227 @@ +import React, { useEffect } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// react-hook-form +import { Controller, useForm } from "react-hook-form"; +// react-color +import { TwitterPicker } from "react-color"; +// headless ui +import { Dialog, Popover, Transition } from "@headlessui/react"; +// services +import stateService from "services/state.service"; +// ui +import { Button, Input, Select, TextArea } from "components/ui"; +// icons +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +// types +import type { IState } from "types"; +// fetch keys +import { STATE_LIST } from "constants/fetch-keys"; +// constants +import { GROUP_CHOICES } from "constants/project"; + +// types +type Props = { + isOpen: boolean; + projectId: string; + handleClose: () => void; +}; + +const defaultValues: Partial = { + name: "", + description: "", + color: "#000000", + group: "backlog", +}; + +export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClose }) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { + register, + formState: { errors, isSubmitting }, + handleSubmit, + watch, + control, + reset, + setError, + } = useForm({ + defaultValues, + }); + + const onClose = () => { + handleClose(); + reset(defaultValues); + }; + + const onSubmit = async (formData: IState) => { + if (!workspaceSlug) return; + + const payload: IState = { + ...formData, + }; + + await stateService + .createState(workspaceSlug as string, projectId, payload) + .then((res) => { + mutate(STATE_LIST(projectId)); + onClose(); + }) + .catch((err) => { + Object.keys(err).map((key) => { + setError(key as keyof IState, { + message: err[key].join(", "), + }); + }); + }); + }; + + return ( + + + +
+ + +
+
+ + +
+
+ + Create State + +
+
+ +
+
+