diff --git a/apiserver/templates/admin/base_site.html b/apiserver/templates/admin/base_site.html index 4fdb5e19b..fd1d89067 100644 --- a/apiserver/templates/admin/base_site.html +++ b/apiserver/templates/admin/base_site.html @@ -17,7 +17,7 @@ color: #FFFFFF; } -

{% trans 'plane Admin' %}

+

{% trans 'Plane Django Admin' %}

{% endblock %}{% block nav-global %}{% endblock %} diff --git a/apps/app/components/account/email-code-form.tsx b/apps/app/components/account/email-code-form.tsx index 98ab10cb7..003d515f4 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/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index 678e17d46..cb4f398b9 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -105,7 +105,7 @@ export const CommandPalette: React.FC = () => { if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); setIsPaletteOpen(true); - } else if (e.ctrlKey && (e.key === "c" || e.key === "C")) { + } else if ((e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C")) { if (e.altKey) { e.preventDefault(); if (!router.query.issueId) return; diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index d4e995403..f5b03267c 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/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index 3a7753366..9fe51b793 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -12,80 +12,97 @@ import { // helpers import { addSpaceIfCamelCase } from "helpers/string.helper"; // types -import { IIssue } from "types"; +import { IIssue, IProjectMember, NestedKeyOf } from "types"; type Props = { - isCollapsed: boolean; - setIsCollapsed: React.Dispatch>; groupedByIssues: { [key: string]: IIssue[]; }; + selectedGroup: NestedKeyOf | null; groupTitle: string; - createdBy: string | null; bgColor?: string; addIssueToState: () => void; + members: IProjectMember[] | undefined; + isCollapsed: boolean; + setIsCollapsed: React.Dispatch>; }; export const BoardHeader: React.FC = ({ - isCollapsed, - setIsCollapsed, groupedByIssues, + selectedGroup, groupTitle, - createdBy, bgColor, addIssueToState, -}) => ( -
-
-
-

{ + const createdBy = + selectedGroup === "created_by" + ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "loading..." + : null; + + let assignees: any; + if (selectedGroup === "assignees") { + assignees = groupTitle.split(","); + assignees = assignees + .map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name) + .join(", "); + } + + return ( +
+
+
- {groupTitle === null || groupTitle === "null" - ? "None" - : createdBy - ? createdBy - : addSpaceIfCamelCase(groupTitle)} -

- {groupedByIssues[groupTitle].length} +

+ {selectedGroup === "created_by" + ? createdBy + : selectedGroup === "assignees" + ? assignees + : addSpaceIfCamelCase(groupTitle)} +

+ {groupedByIssues[groupTitle].length} +
+
+ +
+ +
- -
- - -
-
-); + ); +}; diff --git a/apps/app/components/core/board-view/single-board.tsx b/apps/app/components/core/board-view/single-board.tsx index e7cb49798..b9600a47c 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 @@ -55,11 +59,6 @@ export const SingleBoard: React.FC = ({ const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); - const createdBy = - selectedGroup === "created_by" - ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "loading..." - : null; - if (selectedGroup === "priority") groupTitle === "high" ? (bgColor = "#dc2626") @@ -77,11 +76,12 @@ export const SingleBoard: React.FC = ({ {(provided, snapshot) => ( @@ -97,7 +97,9 @@ export const SingleBoard: React.FC = ({ key={issue.id} draggableId={issue.id} index={index} - isDragDisabled={isNotAllowed || selectedGroup === "created_by"} + isDragDisabled={ + isNotAllowed || selectedGroup === "created_by" || selectedGroup === "assignees" + } > {(provided, snapshot) => ( = ({ 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 945592788..a19c683e2 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 2edb7f804..b3278ec3e 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/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index 4309b2d33..42064f786 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -50,9 +50,17 @@ export const SingleList: React.FC = ({ const createdBy = selectedGroup === "created_by" - ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "loading..." + ? members?.find((m) => m.member.id === groupTitle)?.member.first_name ?? "Loading..." : null; + let assignees: any; + if (selectedGroup === "assignees") { + assignees = groupTitle.split(","); + assignees = assignees + .map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name) + .join(", "); + } + return ( {({ open }) => ( @@ -67,10 +75,10 @@ export const SingleList: React.FC = ({ {selectedGroup !== null ? (

- {groupTitle === null || groupTitle === "null" - ? "None" - : createdBy + {selectedGroup === "created_by" ? createdBy + : selectedGroup === "assignees" + ? assignees : addSpaceIfCamelCase(groupTitle)}

) : ( diff --git a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx index ea8e1e401..8fb073f54 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 5e034822a..3194481b6 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 2d4e5a179..f0a1d2d64 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/issues/sidebar-select/cycle.tsx b/apps/app/components/issues/sidebar-select/cycle.tsx index 353bc5121..b1243fe98 100644 --- a/apps/app/components/issues/sidebar-select/cycle.tsx +++ b/apps/app/components/issues/sidebar-select/cycle.tsx @@ -82,14 +82,14 @@ export const SidebarCycleSelect: React.FC = ({ {cycles ? ( cycles.length > 0 ? ( <> - - None - {cycles.map((option) => ( {option.name} ))} + + None + ) : (
No cycles found
diff --git a/apps/app/components/issues/sidebar-select/module.tsx b/apps/app/components/issues/sidebar-select/module.tsx index e57688887..44bef4d62 100644 --- a/apps/app/components/issues/sidebar-select/module.tsx +++ b/apps/app/components/issues/sidebar-select/module.tsx @@ -81,14 +81,14 @@ export const SidebarModuleSelect: React.FC = ({ {modules ? ( modules.length > 0 ? ( <> - - None - {modules.map((option) => ( {option.name} ))} + + None + ) : (
No modules found
diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index c321f0a31..3ab331244 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/router"; @@ -144,6 +144,12 @@ export const IssueDetailsSidebar: React.FC = ({ [workspaceSlug, projectId, issueId, issueDetail] ); + useEffect(() => { + if (!createLabelForm) return; + + reset(); + }, [createLabelForm, reset]); + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -431,24 +437,25 @@ export const IssueDetailsSidebar: React.FC = ({ )} /> - + {!isNotAllowed && ( + + )}
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 000000000..89c286012 --- /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 d407cd074..db02d29f0 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 317f49a68..05ed67089 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 6bd59d14f..a53fc4353 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -1,18 +1,18 @@ import React, { useState } from "react"; import Link from "next/link"; -import Image from "next/image"; import { useRouter } from "next/router"; // components import { DeleteModuleModal } from "components/modules"; +// ui +import { AssigneesList, Avatar } from "components/ui"; // icons import { CalendarDaysIcon, TrashIcon } from "@heroicons/react/24/outline"; -import User from "public/user.png"; // helpers import { renderShortNumericDateFormat } from "helpers/date-time.helper"; // types -import { IModule, SelectModuleType } from "types"; +import { IModule } from "types"; // common import { MODULE_STATUS } from "constants/module"; @@ -21,132 +21,73 @@ type Props = { }; export const SingleModuleCard: React.FC = ({ module }) => { + 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.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 000000000..5986b4e53 --- /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 + +
+
+ +
+
+