diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 9a1b96b2a4..2c3c464624 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -11,6 +11,7 @@ type Props = { states: IState[] | undefined; members: IProjectMember[] | undefined; addIssueToState: (groupTitle: string, stateId: string | null) => void; + makeIssueCopy: (issue: IIssue) => void; handleEditIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; @@ -25,6 +26,7 @@ export const AllBoards: React.FC = ({ states, members, addIssueToState, + makeIssueCopy, handleEditIssue, openIssuesListModal, handleDeleteIssue, @@ -37,49 +39,46 @@ export const AllBoards: React.FC = ({ return ( <> {groupedByIssues ? ( -
-
-
-
- {Object.keys(groupedByIssues).map((singleGroup, index) => { - const currentState = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup) - : null; +
+
+ {Object.keys(groupedByIssues).map((singleGroup, index) => { + const currentState = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup) + : null; - const stateId = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.id ?? null - : null; + const stateId = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.id ?? null + : null; - const bgColor = - selectedGroup === "state_detail.name" - ? states?.find((s) => s.name === singleGroup)?.color - : "#000000"; + const bgColor = + selectedGroup === "state_detail.name" + ? states?.find((s) => s.name === singleGroup)?.color + : "#000000"; - return ( - addIssueToState(singleGroup, stateId)} - handleDeleteIssue={handleDeleteIssue} - openIssuesListModal={openIssuesListModal ?? null} - orderBy={orderBy} - handleTrashBox={handleTrashBox} - removeIssue={removeIssue} - userAuth={userAuth} - /> - ); - })} -
-
+ return ( + 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 4aafda62ab..d1d3154c51 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -29,6 +29,7 @@ type Props = { selectedGroup: NestedKeyOf | null; members: IProjectMember[] | undefined; handleEditIssue: (issue: IIssue) => void; + makeIssueCopy: (issue: IIssue) => void; addIssueToState: () => void; handleDeleteIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; @@ -47,6 +48,7 @@ export const SingleBoard: React.FC = ({ selectedGroup, members, handleEditIssue, + makeIssueCopy, addIssueToState, handleDeleteIssue, openIssuesListModal, @@ -91,7 +93,7 @@ export const SingleBoard: React.FC = ({ {(provided, snapshot) => (
= ({
This board is ordered by {replaceUnderscoreIfSnakeCase(orderBy ?? "")}
@@ -132,6 +134,7 @@ export const SingleBoard: React.FC = ({ selectedGroup={selectedGroup} properties={properties} editIssue={() => handleEditIssue(issue)} + makeIssueCopy={() => makeIssueCopy(issue)} handleDeleteIssue={handleDeleteIssue} orderBy={orderBy} handleTrashBox={handleTrashBox} @@ -153,7 +156,7 @@ export const SingleBoard: React.FC = ({ {type === "issue" ? (
)} -
-

All Emojis

+
+

All Emojis

{emojis.map((emoji) => (
+ } + onChange={onChange} + noChevron + > + {PRIORITIES.map((priority) => ( + +
+
+ {getPriorityIcon(priority)} + {priority ?? "None"} +
+
+
+ ))} + ); diff --git a/apps/app/components/issues/select/project.tsx b/apps/app/components/issues/select/project.tsx index 39b6b470df..be35098e0d 100644 --- a/apps/app/components/issues/select/project.tsx +++ b/apps/app/components/issues/select/project.tsx @@ -1,11 +1,9 @@ -import { FC, Fragment } from "react"; - import { useRouter } from "next/router"; import useSWR from "swr"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; +// ui +import { CustomSelect } from "components/ui"; // icons import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; // services @@ -19,7 +17,7 @@ export interface IssueProjectSelectProps { setActiveProject: React.Dispatch>; } -export const IssueProjectSelect: FC = ({ +export const IssueProjectSelect: React.FC = ({ value, onChange, setActiveProject, @@ -34,71 +32,35 @@ export const IssueProjectSelect: FC = ({ ); return ( - <> - { - onChange(val); - setActiveProject(val); - }} - > - {({ open }) => ( - <> -
- - - - {projects?.find((i) => i.id === value)?.identifier ?? "Project"} - - - - - -
- {projects ? ( - projects.length > 0 ? ( - projects.map((project) => ( - - `${active ? "bg-indigo-50" : ""} ${ - selected ? "bg-indigo-50 font-medium" : "" - } cursor-pointer select-none p-2 text-gray-900` - } - value={project.id} - > - {({ selected }) => ( - <> - - {project.name} - - - )} - - )) - ) : ( -

No projects found!

- ) - ) : ( -
Loading...
- )} -
-
-
-
- - )} -
- + + + + {projects?.find((i) => i.id === value)?.identifier ?? "Project"} + + + } + onChange={(val: string) => { + onChange(val); + setActiveProject(val); + }} + noChevron + > + {projects ? ( + projects.length > 0 ? ( + projects.map((project) => ( + + <>{project.name} + + )) + ) : ( +

No projects found!

+ ) + ) : ( +
Loading...
+ )} +
); }; diff --git a/apps/app/components/issues/select/state.tsx b/apps/app/components/issues/select/state.tsx index 41f498ec0f..eca350fe6d 100644 --- a/apps/app/components/issues/select/state.tsx +++ b/apps/app/components/issues/select/state.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { useRouter } from "next/router"; @@ -6,20 +6,15 @@ import useSWR from "swr"; // services import stateService from "services/state.service"; -// headless ui -import { - Squares2X2Icon, - PlusIcon, - MagnifyingGlassIcon, - CheckIcon, -} from "@heroicons/react/24/outline"; +// ui +import { CustomSearchSelect } from "components/ui"; // icons -import { Combobox, Transition } from "@headlessui/react"; +import { PlusIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; +import { getStateGroupIcon } from "components/icons"; // helpers import { getStatesList } from "helpers/state.helper"; // fetch keys import { STATE_LIST } from "constants/fetch-keys"; -import { getStateGroupIcon } from "components/icons"; type Props = { setIsOpen: React.Dispatch>; @@ -30,8 +25,6 @@ type Props = { export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, projectId }) => { // states - const [query, setQuery] = useState(""); - const router = useRouter(); const { workspaceSlug } = router.query; @@ -45,123 +38,41 @@ export const IssueStateSelect: React.FC = ({ setIsOpen, value, onChange, const options = states?.map((state) => ({ value: state.id, - display: state.name, - color: state.color, - group: state.group, + query: state.name, + content: ( +
+ {getStateGroupIcon(state.group, "16", "16", state.color)} + {state.name} +
+ ), })); - const filteredOptions = - query === "" - ? options - : options?.filter((option) => option.display.toLowerCase().includes(query.toLowerCase())); + const selectedOption = states?.find((s) => s.id === value); - const currentOption = options?.find((option) => option.value === value); return ( - onChange(val)} - className="relative flex-shrink-0" - > - {({ open }: any) => ( - <> - - `flex items-center text-xs cursor-pointer border rounded-md shadow-sm duration-200 - ${ - open ? "outline-none border-theme bg-theme/5 ring-1 ring-theme " : "hover:bg-theme/5" - }` - } - > - {value && value !== "" ? ( - - {currentOption && currentOption.group - ? getStateGroupIcon(currentOption.group, "16", "16", currentOption.color) - : ""} - {currentOption?.display} - - ) : ( - - - {currentOption?.display || "State"} - - )} - - - - -
- - setQuery(event.target.value)} - placeholder="Search States" - displayValue={(assigned: any) => assigned?.name} - /> -
-
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `${ - active ? "bg-gray-200" : "" - } group flex min-w-[14rem] cursor-pointer select-none items-center gap-2 truncate rounded px-1 py-1.5 text-gray-600` - } - value={option.value} - > - {({ selected, active }) => - states && ( -
-
- {getStateGroupIcon(option.group, "16", "16", option.color)} - {option.display} -
-
- -
-
- ) - } -
- )) - ) : ( -

No states found

- ) - ) : ( -

Loading...

- )} - -
-
-
- - )} -
+ onChange={onChange} + options={options} + label={ +
+ + {selectedOption && + getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)} + {selectedOption?.name ?? "State"} +
+ } + footerOption={ + + } + noChevron + /> ); }; diff --git a/apps/app/components/issues/sidebar-select/assignee.tsx b/apps/app/components/issues/sidebar-select/assignee.tsx index 2d8088059f..0d45143a35 100644 --- a/apps/app/components/issues/sidebar-select/assignee.tsx +++ b/apps/app/components/issues/sidebar-select/assignee.tsx @@ -1,41 +1,57 @@ import React from "react"; -import Image from "next/image"; import { useRouter } from "next/router"; import useSWR from "swr"; -// react-hook-form -import { Control, Controller } from "react-hook-form"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; // services -import { UserGroupIcon } from "@heroicons/react/24/outline"; -import workspaceService from "services/workspace.service"; -// hooks +import projectService from "services/project.service"; // ui -import { AssigneesList } from "components/ui/avatar"; -import { Spinner } from "components/ui"; +import { CustomSearchSelect } from "components/ui"; +import { AssigneesList, Avatar } from "components/ui/avatar"; +// icons +import { UserGroupIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, UserAuth } from "types"; -// constants -import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; +import { UserAuth } from "types"; +// fetch-keys +import { PROJECT_MEMBERS } from "constants/fetch-keys"; type Props = { - control: Control; - submitChanges: (formData: Partial) => void; + value: string[]; + onChange: (val: string[]) => void; userAuth: UserAuth; }; -export const SidebarAssigneeSelect: React.FC = ({ control, submitChanges, userAuth }) => { +export const SidebarAssigneeSelect: React.FC = ({ value, onChange, userAuth }) => { const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; - const { data: people } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null, - workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null + const { data: members } = useSWR( + workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, + workspaceSlug && projectId + ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + : null ); + const options = + members?.map((member) => ({ + value: member.member.id, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email} +
+ ), + })) ?? []; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -45,93 +61,24 @@ export const SidebarAssigneeSelect: React.FC = ({ control, submitChanges,

Assignees

- ( - { - submitChanges({ assignees_list: value }); - }} - className="flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( -
- -
- {value && Array.isArray(value) ? ( - - ) : null} -
-
- - - -
- {people ? ( - people.length > 0 ? ( - people.map((option) => ( - - `${active || selected ? "bg-indigo-50" : ""} ${ - selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900` - } - value={option.member.id} - > - {option.member.avatar && option.member.avatar !== "" ? ( -
- avatar -
- ) : ( -
- {option.member.first_name && option.member.first_name !== "" - ? option.member.first_name.charAt(0) - : option.member.email.charAt(0)} -
- )} - {option.member.first_name && option.member.first_name !== "" - ? option.member.first_name - : option.member.email} -
- )) - ) : ( -
No assignees found
- ) - ) : ( - - )} -
-
-
+ + {value && value.length > 0 && Array.isArray(value) ? ( +
+ + {value.length} Assignees
+ ) : ( + "No assignees" )} - - )} +
+ } + options={options} + onChange={onChange} + multiple + disabled={isNotAllowed} />
diff --git a/apps/app/components/issues/sidebar-select/cycle.tsx b/apps/app/components/issues/sidebar-select/cycle.tsx index 127c78ebac..2d12514545 100644 --- a/apps/app/components/issues/sidebar-select/cycle.tsx +++ b/apps/app/components/issues/sidebar-select/cycle.tsx @@ -65,26 +65,21 @@ export const SidebarCycleSelect: React.FC = ({
- - {issueCycle ? issueCycle.cycle_detail.name : "None"} - - + {issueCycle ? issueCycle.cycle_detail.name : "None"} + } value={issueCycle?.cycle_detail.id} onChange={(value: any) => { - value === null + !value ? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "") : handleCycleChange(cycles?.find((c) => c.id === value) as ICycle); }} + width="w-full" disabled={isNotAllowed} > {cycles ? ( @@ -97,11 +92,7 @@ export const SidebarCycleSelect: React.FC = ({ ))} - - - None - - + 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 90661a0df0..977ae76495 100644 --- a/apps/app/components/issues/sidebar-select/module.tsx +++ b/apps/app/components/issues/sidebar-select/module.tsx @@ -64,26 +64,21 @@ export const SidebarModuleSelect: React.FC = ({
m.id === issueModule?.module)?.name ?? "None"} + - - {modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"} - - + {modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"} + } value={issueModule?.module_detail?.id} onChange={(value: any) => { - value === null + !value ? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "") : handleModuleChange(modules?.find((m) => m.id === value) as IModule); }} + width="w-full" disabled={isNotAllowed} > {modules ? ( @@ -96,11 +91,7 @@ export const SidebarModuleSelect: React.FC = ({ ))} - - - None - - + None ) : (
No modules found
diff --git a/apps/app/components/issues/sidebar-select/priority.tsx b/apps/app/components/issues/sidebar-select/priority.tsx index 7583f16f4f..b3464a1065 100644 --- a/apps/app/components/issues/sidebar-select/priority.tsx +++ b/apps/app/components/issues/sidebar-select/priority.tsx @@ -1,24 +1,22 @@ import React from "react"; -// react-hook-form -import { Control, Controller } from "react-hook-form"; // ui import { CustomSelect } from "components/ui"; // icons import { ChartBarIcon } from "@heroicons/react/24/outline"; import { getPriorityIcon } from "components/icons/priority-icon"; // types -import { IIssue, UserAuth } from "types"; +import { UserAuth } from "types"; // constants import { PRIORITIES } from "constants/project"; type Props = { - control: Control; - submitChanges: (formData: Partial) => void; + value: string | null; + onChange: (val: string) => void; userAuth: UserAuth; }; -export const SidebarPrioritySelect: React.FC = ({ control, submitChanges, userAuth }) => { +export const SidebarPrioritySelect: React.FC = ({ value, onChange, userAuth }) => { const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -28,38 +26,31 @@ export const SidebarPrioritySelect: React.FC = ({ control, submitChanges,

Priority

- ( - - {getPriorityIcon(value && value !== "" ? value ?? "" : "None", "text-sm")} - {value && value !== "" ? value : "None"} - - } - value={value} - onChange={(value: any) => { - submitChanges({ priority: value }); - }} - disabled={isNotAllowed} + - {PRIORITIES.map((option) => ( - - <> - {getPriorityIcon(option, "text-sm")} - {option ?? "None"} - - - ))} - - )} - /> + {getPriorityIcon(value && value !== "" ? value ?? "" : "None", "text-sm")} + {value && value !== "" ? value : "None"} + + } + value={value} + onChange={onChange} + width="w-full" + disabled={isNotAllowed} + > + {PRIORITIES.map((option) => ( + + <> + {getPriorityIcon(option, "text-sm")} + {option ?? "None"} + + + ))} +
); diff --git a/apps/app/components/issues/sidebar-select/state.tsx b/apps/app/components/issues/sidebar-select/state.tsx index 0ed3a04bb7..13ecd3c620 100644 --- a/apps/app/components/issues/sidebar-select/state.tsx +++ b/apps/app/components/issues/sidebar-select/state.tsx @@ -4,28 +4,28 @@ import { useRouter } from "next/router"; import useSWR from "swr"; -// react-hook-form -import { Control, Controller } from "react-hook-form"; // services import stateService from "services/state.service"; // ui import { Spinner, CustomSelect } from "components/ui"; // icons import { Squares2X2Icon } from "@heroicons/react/24/outline"; +import { getStateGroupIcon } from "components/icons"; // helpers import { getStatesList } from "helpers/state.helper"; +import { addSpaceIfCamelCase } from "helpers/string.helper"; // types -import { IIssue, UserAuth } from "types"; +import { UserAuth } from "types"; // constants import { STATE_LIST } from "constants/fetch-keys"; type Props = { - control: Control; - submitChanges: (formData: Partial) => void; + value: string; + onChange: (val: string) => void; userAuth: UserAuth; }; -export const SidebarStateSelect: React.FC = ({ control, submitChanges, userAuth }) => { +export const SidebarStateSelect: React.FC = ({ value, onChange, userAuth }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -37,6 +37,8 @@ export const SidebarStateSelect: React.FC = ({ control, submitChanges, us ); const states = getStatesList(stateGroups ?? {}); + const selectedState = states?.find((s) => s.id === value); + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -46,60 +48,40 @@ export const SidebarStateSelect: React.FC = ({ control, submitChanges, us

State

- ( - - {value ? ( - <> - option.id === value)?.color, - }} - /> - {states?.find((option) => option.id === value)?.name} - - ) : ( - "None" - )} - - } - value={value} - onChange={(value: any) => { - submitChanges({ state: value }); - }} - disabled={isNotAllowed} - > - {states ? ( - states.length > 0 ? ( - states.map((option) => ( - - <> - {option.color && ( - - )} - {option.name} - - - )) - ) : ( -
No states found
- ) - ) : ( - + + {getStateGroupIcon( + selectedState?.group ?? "backlog", + "16", + "16", + selectedState?.color ?? "" )} - + {addSpaceIfCamelCase(selectedState?.name ?? "")} +
+ } + value={value} + onChange={onChange} + width="w-full" + disabled={isNotAllowed} + > + {states ? ( + states.length > 0 ? ( + states.map((state) => ( + + <> + {getStateGroupIcon(state.group, "16", "16", state.color)} + {state.name} + + + )) + ) : ( +
No states found
+ ) + ) : ( + )} - /> +
); diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index 249927f25c..5632dc015b 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -77,17 +77,6 @@ export const IssueDetailsSidebar: React.FC = ({ const { setToastAlert } = useToast(); - console.log("isseu details: ", issueDetail); - - // const { data: issueLinks } = 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: issues } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) @@ -228,7 +217,7 @@ export const IssueDetailsSidebar: React.FC = ({ isOpen={deleteIssueModal} data={issueDetail ?? null} /> -
+

{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id} @@ -254,20 +243,38 @@ export const IssueDetailsSidebar: React.FC = ({

- ( + submitChanges({ state: val })} + userAuth={userAuth} + /> + )} /> - ( + submitChanges({ assignees_list: val })} + userAuth={userAuth} + /> + )} /> - ( + submitChanges({ priority: val })} + userAuth={userAuth} + /> + )} />
@@ -453,8 +460,8 @@ export const IssueDetailsSidebar: React.FC = ({ ); } else return ( -
-
+
+
{" "} {label.name}
diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index cb482dfa50..6e9c900fd1 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -4,12 +4,12 @@ import { useRouter } from "next/router"; import useSWR from "swr"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; // services import projectService from "services/project.service"; // ui -import { AssigneesList, Avatar, Tooltip } from "components/ui"; +import { AssigneesList, Avatar, CustomSearchSelect, Tooltip } from "components/ui"; +// icons +import { UserGroupIcon } from "@heroicons/react/24/outline"; // types import { IIssue } from "types"; // fetch-keys @@ -18,6 +18,7 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys"; type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial) => void; + position?: "left" | "right"; selfPositioned?: boolean; tooltipPosition?: "left" | "right"; isNotAllowed: boolean; @@ -26,6 +27,7 @@ type Props = { export const ViewAssigneeSelect: React.FC = ({ issue, partialUpdateIssue, + position = "left", selfPositioned = false, tooltipPosition = "right", isNotAllowed, @@ -40,9 +42,27 @@ export const ViewAssigneeSelect: React.FC = ({ : null ); + const options = + members?.map((member) => ({ + value: member.member.id, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email} +
+ ), + })) ?? []; + return ( - { const newData = issue.assignees ?? []; @@ -50,69 +70,46 @@ export const ViewAssigneeSelect: React.FC = ({ if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); else newData.push(data); - partialUpdateIssue({ assignees_list: newData }); + partialUpdateIssue({ assignees_list: data }); }} - className={`group ${!selfPositioned ? "relative" : ""} flex-shrink-0`} - disabled={isNotAllowed} - > - {({ open }) => ( -
- - 0 - ? issue.assignee_details - .map((assignee) => - assignee?.first_name !== "" ? assignee?.first_name : assignee?.email - ) - .join(", ") - : "No Assignee" - } - > -
- -
-
-
- - 0 + ? issue.assignee_details + .map((assignee) => + assignee?.first_name !== "" ? assignee?.first_name : assignee?.email + ) + .join(", ") + : "No Assignee" + } + > +
- - {members?.map((member) => ( - - `flex items-center gap-x-1 cursor-pointer select-none p-2 whitespace-nowrap ${ - active ? "bg-indigo-50" : "" - } ${ - selected || issue.assignees?.includes(member.member.id) - ? "bg-indigo-50 font-medium" - : "font-normal" - }` - } - value={member.member.id} - > - - {member.member.first_name && member.member.first_name !== "" - ? member.member.first_name - : member.member.email} - - ))} - - -
- )} - + {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( +
+ + {issue.assignees.length} Assignees +
+ ) : ( +
+ + Assignee +
+ )} +
+ + } + multiple + noChevron + position={position} + disabled={isNotAllowed} + /> ); }; diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index f47ea934d5..6ca81037e3 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -12,6 +12,7 @@ import { PRIORITIES } from "constants/project"; type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial) => void; + position?: "left" | "right"; selfPositioned?: boolean; isNotAllowed: boolean; }; @@ -19,19 +20,18 @@ type Props = { export const ViewPrioritySelect: React.FC = ({ issue, partialUpdateIssue, + position = "left", selfPositioned = false, isNotAllowed, }) => ( { - partialUpdateIssue({ priority: data }); - }} + onChange={(data: string) => partialUpdateIssue({ priority: data })} maxHeight="md" customButton={ -
- )} - - ) : ( -
- -
- - - - -
-
- - - - -
-
+ ); + })}
)} +
+ {!sidebarCollapse &&
Projects
} + {projects ? ( + <> + {normalProjects.length > 0 ? ( + normalProjects.map((project) => ( + + {({ open }) => ( + <> + +
+ {project.icon ? ( + + {String.fromCodePoint(parseInt(project.icon))} + + ) : ( + + {project?.name.charAt(0)} + + )} + + {!sidebarCollapse && ( +

+ {truncateText(project?.name, 20)} +

+ )} +
+ +
+ {!sidebarCollapse && ( + + handleCopyText(project.id)}> + Copy project link + + + )} + {!sidebarCollapse && ( + + + + )} +
+
+ + + + {navigation(workspaceSlug as string, project?.id).map((item) => { + if (item.name === "Cycles" && !project.cycle_view) return; + if (item.name === "Modules" && !project.module_view) return; + + return ( + + +
+
+ {!sidebarCollapse && item.name} +
+ + ); + })} +
+
+ + )} +
+ )) + ) : ( +
+ {!sidebarCollapse && ( +

You don{"'"}t have any project yet

+ )} + +
+ )} + + ) : ( +
+ +
+ + + + +
+
+ + + + +
+
+
+ )} +
); diff --git a/apps/app/components/project/single-project-card.tsx b/apps/app/components/project/single-project-card.tsx index b29c9e8a94..99ee697930 100644 --- a/apps/app/components/project/single-project-card.tsx +++ b/apps/app/components/project/single-project-card.tsx @@ -20,9 +20,9 @@ import { StarIcon } from "@heroicons/react/20/solid"; import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types -import type { IProject } from "types"; +import type { IFavoriteProject, IProject } from "types"; // fetch-keys -import { PROJECTS_LIST } from "constants/fetch-keys"; +import { FAVORITE_PROJECTS_LIST, PROJECTS_LIST } from "constants/fetch-keys"; export type ProjectCardProps = { project: IProject; @@ -63,6 +63,7 @@ export const SingleProjectCard: React.FC = ({ })), false ); + mutate(FAVORITE_PROJECTS_LIST(workspaceSlug as string)); setToastAlert({ type: "success", @@ -94,6 +95,11 @@ export const SingleProjectCard: React.FC = ({ })), false ); + mutate( + FAVORITE_PROJECTS_LIST(workspaceSlug as string), + (prevData) => (prevData ?? []).filter((p) => p.project !== project.id), + false + ); setToastAlert({ type: "success", @@ -126,7 +132,7 @@ export const SingleProjectCard: React.FC = ({ return ( <> {members ? ( -
+
@@ -149,16 +155,16 @@ export const SingleProjectCard: React.FC = ({ e.stopPropagation(); setToJoinProject(project.id); }} - className="flex cursor-pointer items-center gap-1 bg-green-600 px-2 py-1 rounded text-xs" + className="flex cursor-pointer items-center gap-1 rounded bg-green-600 px-2 py-1 text-xs" > Select to Join ) : ( - Member + Member )} {project.is_favourite && ( - + )} @@ -166,7 +172,7 @@ export const SingleProjectCard: React.FC = ({
-
+
@@ -180,7 +186,7 @@ export const SingleProjectCard: React.FC = ({

{truncateText(project.description ?? "", 100)}

-
+
{ - name: string; - href: string; - icon: any; - }[]; - sidebarCollapse: boolean; -}; - -const ProjectsList: React.FC = ({ navigation, sidebarCollapse }) => { - const [isCreateProjectModal, setCreateProjectModal] = useState(false); - - const router = useRouter(); - - const { workspaceSlug, projectId } = router.query; - - const { setToastAlert } = useToast(); - - const { data: projects } = useSWR( - workspaceSlug ? PROJECTS_LIST(workspaceSlug as string) : null, - () => (workspaceSlug ? projectService.getProjects(workspaceSlug as string) : null) - ); - - return ( - <> - -
- {projects ? ( - <> - {projects.length > 0 ? ( - projects.map((project) => ( - - {({ open }) => ( - <> -
- - {project.icon ? ( - - {String.fromCodePoint(parseInt(project.icon))} - - ) : ( - - {project?.name.charAt(0)} - - )} - - {!sidebarCollapse && ( - - - {project?.name} - - - - - - )} - - {!sidebarCollapse && ( - - - copyTextToClipboard( - `https://app.plane.so/${workspaceSlug}/projects/${project?.id}/issues/` - ).then(() => { - setToastAlert({ - title: "Link Copied", - message: "Link copied to clipboard", - type: "success", - }); - }) - } - > - Copy link - - - )} -
- - - {navigation(workspaceSlug as string, project?.id).map((item) => ( - - - - - ))} - - - - )} -
- )) - ) : ( -
- {!sidebarCollapse && ( -

You don{"'"}t have any project yet

- )} - -
- )} - - ) : ( -
- -
- - - - -
-
- - - - -
-
-
- )} -
- - ); -}; - -export default ProjectsList; diff --git a/apps/app/components/sidebar/workspace-options.tsx b/apps/app/components/sidebar/workspace-options.tsx deleted file mode 100644 index 8dde59f2a2..0000000000 --- a/apps/app/components/sidebar/workspace-options.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useRouter } from "next/router"; -import Link from "next/link"; - -import { - ClipboardDocumentListIcon, - Cog6ToothIcon, - HomeIcon, - RectangleStackIcon, -} from "@heroicons/react/24/outline"; - -type Props = { - sidebarCollapse: boolean; -}; - -const workspaceLinks = (workspaceSlug: string) => [ - { - icon: HomeIcon, - name: "Home", - href: `/${workspaceSlug}`, - }, - { - icon: ClipboardDocumentListIcon, - name: "Projects", - href: `/${workspaceSlug}/projects`, - }, - { - icon: RectangleStackIcon, - name: "My Issues", - href: `/${workspaceSlug}/me/my-issues`, - }, - { - icon: Cog6ToothIcon, - name: "Settings", - href: `/${workspaceSlug}/settings`, - }, -]; - -const WorkspaceOptions: React.FC = ({ sidebarCollapse }) => { - const router = useRouter(); - - const { - query: { workspaceSlug }, - } = router; - - return ( -
-
- {workspaceLinks(workspaceSlug as string).map((link, index) => ( - - - - - ))} -
-
- ); -}; - -export default WorkspaceOptions; diff --git a/apps/app/components/states/create-state-modal.tsx b/apps/app/components/states/create-state-modal.tsx index c3bdd5526d..69208873aa 100644 --- a/apps/app/components/states/create-state-modal.tsx +++ b/apps/app/components/states/create-state-modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { useRouter } from "next/router"; @@ -13,7 +13,7 @@ import { Dialog, Popover, Transition } from "@headlessui/react"; // services import stateService from "services/state.service"; // ui -import { Button, Input, Select, TextArea } from "components/ui"; +import { Button, CustomSelect, Input, TextArea } from "components/ui"; // icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; // types @@ -129,19 +129,25 @@ export const CreateStateModal: React.FC = ({ isOpen, projectId, handleClo />
- - {options.map((option, index) => ( - - ))} - - {error?.message &&
{error.message}
} - -); diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index 5a5e3a6007..53cc1e461a 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -56,7 +56,7 @@ export const WorkspaceHelpSection: FC = (props) => { return (
@@ -132,7 +132,7 @@ export const WorkspaceHelpSection: FC = (props) => { {name} diff --git a/apps/app/components/workspace/send-workspace-invitation-modal.tsx b/apps/app/components/workspace/send-workspace-invitation-modal.tsx index 0446e201ce..81ebacc69a 100644 --- a/apps/app/components/workspace/send-workspace-invitation-modal.tsx +++ b/apps/app/components/workspace/send-workspace-invitation-modal.tsx @@ -1,12 +1,12 @@ import React from "react"; import { mutate } from "swr"; -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; // headless import { Dialog, Transition } from "@headlessui/react"; // services import workspaceService from "services/workspace.service"; // ui -import { Button, Input, Select } from "components/ui"; +import { Button, CustomSelect, Input } from "components/ui"; // hooks import useToast from "hooks/use-toast"; // types @@ -37,6 +37,7 @@ const SendWorkspaceInvitationModal: React.FC = ({ const { setToastAlert } = useToast(); const { + control, register, formState: { errors, isSubmitting }, handleSubmit, @@ -44,7 +45,6 @@ const SendWorkspaceInvitationModal: React.FC = ({ } = useForm({ defaultValues, reValidateMode: "onChange", - mode: "all", }); const handleClose = () => { @@ -98,13 +98,13 @@ const SendWorkspaceInvitationModal: React.FC = ({ leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
- - Members - -
+
+ + Members +

Invite members to work on your workspace.

@@ -129,34 +129,30 @@ const SendWorkspaceInvitationModal: React.FC = ({ />
-