From adbe16f8aec867347df4b8649d3c86e7dc0e1336 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 6 Feb 2023 15:18:57 +0530 Subject: [PATCH] refactor: global select component for issue view --- apps/app/components/command-palette/index.tsx | 3 +- .../components/core/board-view/all-boards.tsx | 37 +- .../core/board-view/single-board.tsx | 190 +++--- .../core/board-view/single-issue.tsx | 552 ++++++------------ .../components/core/issues-view-filter.tsx | 18 +- apps/app/components/core/issues-view.tsx | 1 - .../core/list-view/single-issue.tsx | 414 ++++--------- .../components/core/list-view/single-list.tsx | 40 +- apps/app/components/core/select/assignee.tsx | 94 +++ apps/app/components/core/select/due-date.tsx | 48 ++ apps/app/components/core/select/index.ts | 4 + apps/app/components/core/select/priority.tsx | 97 +++ apps/app/components/core/select/state.tsx | 55 ++ .../components/dnd/StrictModeDroppable.tsx | 5 +- .../components/issues/delete-issue-modal.tsx | 49 +- .../components/issues/my-issues-list-item.tsx | 157 +++-- .../issues/sidebar-select/cycle.tsx | 2 +- .../issues/sidebar-select/module.tsx | 107 ++-- apps/app/components/issues/sidebar.tsx | 21 +- apps/app/constants/index.ts | 3 +- apps/app/contexts/issue-view.context.tsx | 10 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 318 +++++----- .../projects/[projectId]/issues/[issueId].tsx | 1 + apps/app/pages/_app.tsx | 16 +- 24 files changed, 1090 insertions(+), 1152 deletions(-) create mode 100644 apps/app/components/core/select/assignee.tsx create mode 100644 apps/app/components/core/select/due-date.tsx create mode 100644 apps/app/components/core/select/index.ts create mode 100644 apps/app/components/core/select/priority.tsx create mode 100644 apps/app/components/core/select/state.tsx diff --git a/apps/app/components/command-palette/index.tsx b/apps/app/components/command-palette/index.tsx index 9b72b918d..e6138da94 100644 --- a/apps/app/components/command-palette/index.tsx +++ b/apps/app/components/command-palette/index.tsx @@ -75,7 +75,7 @@ const CommandPalette: React.FC = () => { name: "Add new issue...", icon: RectangleStackIcon, hide: !projectId, - shortcut: "I", + shortcut: "C", onClick: () => { setIsIssueModalOpen(true); }, @@ -329,7 +329,6 @@ const CommandPalette: React.FC = () => { /> {action.name} - {action.shortcut} diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 76b20e5fa..a6d99fa0f 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -1,5 +1,5 @@ // react-beautiful-dnd -import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd"; +import { DragDropContext, DropResult } from "react-beautiful-dnd"; // hooks import useIssueView from "hooks/use-issue-view"; // components @@ -31,7 +31,7 @@ export const AllBoards: React.FC = ({ handleOnDragEnd, userAuth, }) => { - const { groupedByIssues, groupByProperty: selectedGroup } = useIssueView(issues); + const { groupedByIssues, groupByProperty: selectedGroup, orderBy } = useIssueView(issues); return ( <> @@ -59,24 +59,21 @@ export const AllBoards: React.FC = ({ : "#000000"; return ( - - {(provided, snapshot) => ( - addIssueToState(singleGroup, stateId)} - handleDeleteIssue={handleDeleteIssue} - openIssuesListModal={openIssuesListModal ?? null} - userAuth={userAuth} - /> - )} - + addIssueToState(singleGroup, stateId)} + handleDeleteIssue={handleDeleteIssue} + openIssuesListModal={openIssuesListModal ?? null} + orderBy={orderBy} + 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 9e8dc1b16..5aa999f87 100644 --- a/apps/app/components/core/board-view/single-board.tsx +++ b/apps/app/components/core/board-view/single-board.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; // react-beautiful-dnd import StrictModeDroppable from "components/dnd/StrictModeDroppable"; -import { Draggable, DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd"; +import { Draggable } from "react-beautiful-dnd"; // hooks import useIssuesProperties from "hooks/use-issue-properties"; // components @@ -17,9 +17,8 @@ import { PlusIcon } from "@heroicons/react/24/outline"; import { IIssue, IProjectMember, NestedKeyOf, UserAuth } from "types"; type Props = { + index: number; type?: "issue" | "cycle" | "module"; - provided: DraggableProvided; - snapshot: DraggableStateSnapshot; bgColor?: string; groupTitle: string; groupedByIssues: { @@ -30,13 +29,13 @@ type Props = { addIssueToState: () => void; handleDeleteIssue: (issue: IIssue) => void; openIssuesListModal?: (() => void) | null; + orderBy: NestedKeyOf | "manual" | null; userAuth: UserAuth; }; export const SingleBoard: React.FC = ({ + index, type, - provided, - snapshot, bgColor, groupTitle, groupedByIssues, @@ -45,6 +44,7 @@ export const SingleBoard: React.FC = ({ addIssueToState, handleDeleteIssue, openIssuesListModal, + orderBy, userAuth, }) => { // collapse/expand @@ -70,101 +70,95 @@ export const SingleBoard: React.FC = ({ : (bgColor = "#ff0000"); return ( -
-
- - - {(provided, snapshot) => ( -
- {groupedByIssues[groupTitle].map((childIssue, index: number) => { - const assignees = [ - ...(childIssue?.assignees_list ?? []), - ...(childIssue?.assignees ?? []), - ]?.map((assignee) => { - const tempPerson = members?.find((p) => p.member.id === assignee)?.member; - - return tempPerson; - }); - - return ( - - {(provided, snapshot) => ( -
- -
- )} -
- ); - })} - {provided.placeholder} - {type === "issue" ? ( - - ) : ( - - - Add issue - - } - className="mt-1" - optionsPosition="left" - noBorder - > - Create new - {openIssuesListModal && ( - - Add an existing issue - + {groupedByIssues[groupTitle].map((issue, index: number) => ( + + ))} + + {provided.placeholder} + + {type === "issue" ? ( + + ) : ( + + + Add issue + + } + className="mt-1" + optionsPosition="left" + noBorder + > + + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + )} - +
)} -
- )} - -
- + + + + )} + ); }; diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 879977fb1..4f7ae0591 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -1,60 +1,57 @@ -import React from "react"; +import React, { useCallback } from "react"; import Link from "next/link"; -import Image from "next/image"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // react-beautiful-dnd -import { DraggableStateSnapshot } from "react-beautiful-dnd"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; +import { + Draggable, + DraggableStateSnapshot, + DraggingStyle, + NotDraggingStyle, +} from "react-beautiful-dnd"; // constants import { TrashIcon } from "@heroicons/react/24/outline"; // services import issuesService from "services/issues.service"; import stateService from "services/state.service"; // components -import { AssigneesList, CustomDatePicker } from "components/ui"; -// helpers -import { findHowManyDaysLeft } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; +import { AssigneeSelect, DueDateSelect, PrioritySelect, StateSelect } from "components/core/select"; // types import { CycleIssueResponse, IIssue, IProjectMember, IssueResponse, - IUserLite, ModuleIssueResponse, + NestedKeyOf, Properties, UserAuth, } from "types"; -// common -import { PRIORITIES } from "constants/"; +// fetch-keys import { STATE_LIST, CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys"; -import { getPriorityIcon } from "constants/global"; type Props = { + index: number; type?: string; issue: IIssue; properties: Properties; - snapshot: DraggableStateSnapshot; - assignees: Partial[] | (Partial | undefined)[]; - people: IProjectMember[] | undefined; + members: IProjectMember[] | undefined; handleDeleteIssue: (issue: IIssue) => void; + orderBy: NestedKeyOf | "manual" | null; userAuth: UserAuth; }; export const SingleBoardIssue: React.FC = ({ + index, type, issue, properties, - snapshot, - assignees, - people, + members, handleDeleteIssue, + orderBy, userAuth, }) => { const router = useRouter(); @@ -67,376 +64,185 @@ export const SingleBoardIssue: React.FC = ({ : null ); - const partialUpdateIssue = (formData: Partial) => { - if (!workspaceSlug || !projectId) return; + const partialUpdateIssue = useCallback( + (formData: Partial) => { + if (!workspaceSlug || !projectId) return; - if (cycleId) - mutate( - CYCLE_ISSUES(cycleId as string), - (prevData) => { - const updatedIssues = (prevData ?? []).map((p) => { - if (p.issue_detail.id === issue.id) { - return { - ...p, - issue_detail: { - ...p.issue_detail, - ...formData, - }, - }; - } + if (cycleId) + mutate( + CYCLE_ISSUES(cycleId as string), + (prevData) => { + const updatedIssues = (prevData ?? []).map((p) => { + if (p.issue_detail.id === issue.id) { + return { + ...p, + issue_detail: { + ...p.issue_detail, + ...formData, + }, + }; + } + return p; + }); + return [...updatedIssues]; + }, + false + ); + + if (moduleId) + mutate( + MODULE_ISSUES(moduleId as string), + (prevData) => { + const updatedIssues = (prevData ?? []).map((p) => { + if (p.issue_detail.id === issue.id) { + return { + ...p, + issue_detail: { + ...p.issue_detail, + ...formData, + }, + }; + } + return p; + }); + return [...updatedIssues]; + }, + false + ); + + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), + (prevData) => ({ + ...(prevData as IssueResponse), + results: (prevData?.results ?? []).map((p) => { + if (p.id === issue.id) return { ...p, ...formData }; return p; - }); - return [...updatedIssues]; - }, - false - ); - - if (moduleId) - mutate( - MODULE_ISSUES(moduleId as string), - (prevData) => { - const updatedIssues = (prevData ?? []).map((p) => { - if (p.issue_detail.id === issue.id) { - return { - ...p, - issue_detail: { - ...p.issue_detail, - ...formData, - }, - }; - } - return p; - }); - return [...updatedIssues]; - }, - false - ); - - mutate( - PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), - (prevData) => ({ - ...(prevData as IssueResponse), - results: (prevData?.results ?? []).map((p) => { - if (p.id === issue.id) return { ...p, ...formData }; - return p; + }), }), - }), - false - ); + false + ); - issuesService - .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) - .then((res) => { - mutate( - cycleId ? CYCLE_ISSUES(cycleId as string) : CYCLE_ISSUES(issue?.issue_cycle?.cycle ?? "") - ); - mutate( - moduleId - ? MODULE_ISSUES(moduleId as string) - : MODULE_ISSUES(issue?.issue_module?.module ?? "") - ); + issuesService + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .then((res) => { + mutate( + cycleId + ? CYCLE_ISSUES(cycleId as string) + : CYCLE_ISSUES(issue?.issue_cycle?.cycle ?? "") + ); + mutate( + moduleId + ? MODULE_ISSUES(moduleId as string) + : MODULE_ISSUES(issue?.issue_module?.module ?? "") + ); - mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); - }) - .catch((error) => { - console.log(error); - }); - }; + mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); + }) + .catch((error) => { + console.log(error); + }); + }, + [workspaceSlug, projectId, cycleId, moduleId, issue] + ); + + function getStyle( + style: DraggingStyle | NotDraggingStyle | undefined, + snapshot: DraggableStateSnapshot + ) { + if (orderBy === "manual") return style; + if (!snapshot.isDragging) return {}; + if (!snapshot.isDropAnimating) { + return style; + } + + return { + ...style, + transitionDuration: `0.001s`, + }; + } const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( -
-
- {!isNotAllowed && ( -
- -
- )} - - - {properties.key && ( -
- {issue.project_detail.identifier}-{issue.sequence_id} + + {(provided, snapshot) => ( +
+
+ {!isNotAllowed && ( +
+
)} -
- {issue.name} -
-
- -
- {properties.priority && ( - { - partialUpdateIssue({ priority: data }); - }} - className="group relative flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( - <> -
- - {getPriorityIcon(issue?.priority ?? "None")} - - - - - {PRIORITIES?.map((priority) => ( - - `flex cursor-pointer select-none items-center gap-2 px-3 py-2 capitalize ${ - active ? "bg-indigo-50" : "bg-white" - }` - } - value={priority} - > - {getPriorityIcon(priority)} - {priority} - - ))} - - + + + {properties.key && ( +
+ {issue.project_detail.identifier}-{issue.sequence_id}
- + )} +
+ {issue.name} +
+
+ +
+ {properties.priority && ( + )} - - )} - {properties.state && ( - { - partialUpdateIssue({ state: data }); - }} - className="group relative flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( - <> -
- - - {addSpaceIfCamelCase(issue.state_detail.name)} - - - - - {states?.map((state) => ( - - `flex cursor-pointer select-none items-center gap-2 px-3 py-2 ${ - active ? "bg-indigo-50" : "bg-white" - }` - } - value={state.id} - > - - {addSpaceIfCamelCase(state.name)} - - ))} - - -
- + {properties.state && ( + )} -
- )} - {properties.due_date && ( -
- - partialUpdateIssue({ - target_date: val, - }) - } - className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"} - /> - {/* { - partialUpdateIssue({ - target_date: val - ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}` - : null, - }); - }} - dateFormat="dd-MM-yyyy" - className={`cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ - issue?.target_date ? "w-[4.5rem]" : "w-[3rem] text-center" - }`} - isClearable - /> */} -
- )} - {properties.sub_issue_count && ( -
- {issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"} -
- )} - {properties.assignee && ( - { - const newData = issue.assignees ?? []; - - if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); - else newData.push(data); - - partialUpdateIssue({ assignees_list: newData }); - }} - className="group relative flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( -
- -
- -
-
- - - - {people?.map((person) => ( - - `cursor-pointer select-none p-2 ${active ? "bg-indigo-50" : "bg-white"}` - } - value={person.member.id} - > -
- {person.member.avatar && person.member.avatar !== "" ? ( -
- avatar -
- ) : ( -
- {person.member.first_name && person.member.first_name !== "" - ? person.member.first_name.charAt(0) - : person.member.email.charAt(0)} -
- )} -

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

-
-
- ))} -
-
+ {properties.due_date && ( + + )} + {properties.sub_issue_count && ( +
+ {issue.sub_issues_count}{" "} + {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
)} - - )} + {properties.assignee && ( + + )} +
+
-
-
+ )} + ); }; diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 69d5f191e..3920d0e91 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -104,14 +104,16 @@ export const IssuesFilterView: React.FC = ({ issues }) => { } width="lg" > - {groupByOptions.map((option) => ( - setGroupByProperty(option.key)} - > - {option.name} - - ))} + {groupByOptions.map((option) => + issueView === "kanban" && option.key === null ? null : ( + setGroupByProperty(option.key)} + > + {option.name} + + ) + )}
diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 263bf0538..3339bfdae 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -125,7 +125,6 @@ export const IssuesView: React.FC = ({ }); } else { const draggedItem = groupedByIssues[source.droppableId][source.index]; - console.log(draggedItem); if (source.droppableId !== destination.droppableId) { const sourceGroup = source.droppableId; // source group id const destinationGroup = destination.droppableId; // destination group id diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index dbadd84f7..9ed4fa156 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -1,47 +1,35 @@ -import React from "react"; +import React, { useCallback } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; -// headless ui -import { Listbox, Transition } from "@headlessui/react"; // services import issuesService from "services/issues.service"; -import workspaceService from "services/workspace.service"; import stateService from "services/state.service"; +// components +import { AssigneeSelect, DueDateSelect, PrioritySelect, StateSelect } from "components/core/select"; // ui -import { CustomMenu, CustomSelect, AssigneesList, Avatar, CustomDatePicker } from "components/ui"; -// helpers -import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; +import { CustomMenu } from "components/ui"; // types import { CycleIssueResponse, IIssue, + IProjectMember, IssueResponse, - IWorkspaceMember, ModuleIssueResponse, Properties, UserAuth, } from "types"; // fetch-keys -import { - CYCLE_ISSUES, - MODULE_ISSUES, - PROJECT_ISSUES_LIST, - STATE_LIST, - WORKSPACE_MEMBERS, -} from "constants/fetch-keys"; -// constants -import { getPriorityIcon } from "constants/global"; -import { PRIORITIES } from "constants/"; +import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST, STATE_LIST } from "constants/fetch-keys"; type Props = { type?: string; issue: IIssue; properties: Properties; + members: IProjectMember[] | undefined; editIssue: () => void; removeIssue?: (() => void) | null; handleDeleteIssue: (issue: IIssue) => void; @@ -52,6 +40,7 @@ export const SingleListIssue: React.FC = ({ type, issue, properties, + members, editIssue, removeIssue, handleDeleteIssue, @@ -67,86 +56,86 @@ export const SingleListIssue: React.FC = ({ : null ); - const { data: people } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS : null, - workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null - ); + const partialUpdateIssue = useCallback( + (formData: Partial) => { + if (!workspaceSlug || !projectId) return; - const partialUpdateIssue = (formData: Partial) => { - if (!workspaceSlug || !projectId) return; + if (cycleId) + mutate( + CYCLE_ISSUES(cycleId as string), + (prevData) => { + const updatedIssues = (prevData ?? []).map((p) => { + if (p.issue_detail.id === issue.id) { + return { + ...p, + issue_detail: { + ...p.issue_detail, + ...formData, + }, + }; + } + return p; + }); + return [...updatedIssues]; + }, + false + ); - if (cycleId) - mutate( - CYCLE_ISSUES(cycleId as string), - (prevData) => { - const updatedIssues = (prevData ?? []).map((p) => { - if (p.issue_detail.id === issue.id) { - return { - ...p, - issue_detail: { - ...p.issue_detail, - ...formData, - }, - }; - } + if (moduleId) + mutate( + MODULE_ISSUES(moduleId as string), + (prevData) => { + const updatedIssues = (prevData ?? []).map((p) => { + if (p.issue_detail.id === issue.id) { + return { + ...p, + issue_detail: { + ...p.issue_detail, + ...formData, + }, + }; + } + return p; + }); + return [...updatedIssues]; + }, + false + ); + + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), + (prevData) => ({ + ...(prevData as IssueResponse), + results: (prevData?.results ?? []).map((p) => { + if (p.id === issue.id) return { ...p, ...formData }; return p; - }); - return [...updatedIssues]; - }, - false - ); - - if (moduleId) - mutate( - MODULE_ISSUES(moduleId as string), - (prevData) => { - const updatedIssues = (prevData ?? []).map((p) => { - if (p.issue_detail.id === issue.id) { - return { - ...p, - issue_detail: { - ...p.issue_detail, - ...formData, - }, - }; - } - return p; - }); - return [...updatedIssues]; - }, - false - ); - - mutate( - PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), - (prevData) => ({ - ...(prevData as IssueResponse), - results: (prevData?.results ?? []).map((p) => { - if (p.id === issue.id) return { ...p, ...formData }; - return p; + }), }), - }), - false - ); + false + ); - issuesService - .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) - .then((res) => { - mutate( - cycleId ? CYCLE_ISSUES(cycleId as string) : CYCLE_ISSUES(issue?.issue_cycle?.cycle ?? "") - ); - mutate( - moduleId - ? MODULE_ISSUES(moduleId as string) - : MODULE_ISSUES(issue?.issue_module?.module ?? "") - ); + issuesService + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .then((res) => { + mutate( + cycleId + ? CYCLE_ISSUES(cycleId as string) + : CYCLE_ISSUES(issue?.issue_cycle?.cycle ?? "") + ); + mutate( + moduleId + ? MODULE_ISSUES(moduleId as string) + : MODULE_ISSUES(issue?.issue_module?.module ?? "") + ); - mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); - }) - .catch((error) => { - console.log(error); - }); - }; + mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)); + }) + .catch((error) => { + console.log(error); + }); + }, + [workspaceSlug, projectId, cycleId, moduleId, issue] + ); const isNotAllowed = userAuth.isGuest || userAuth.isViewer; @@ -172,156 +161,26 @@ export const SingleListIssue: React.FC = ({
{properties.priority && ( - { - partialUpdateIssue({ priority: data }); - }} - className="group relative flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( - <> -
- - {getPriorityIcon( - issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", - "text-sm" - )} - - - - - {PRIORITIES?.map((priority) => ( - - `flex cursor-pointer select-none items-center gap-x-2 px-3 py-2 capitalize ${ - active ? "bg-indigo-50" : "bg-white" - }` - } - value={priority} - > - {getPriorityIcon(priority, "text-sm")} - {priority ?? "None"} - - ))} - - -
-
-
Priority
-
- {issue.priority ?? "None"} -
-
- - )} -
+ )} {properties.state && ( - - - {addSpaceIfCamelCase(issue.state_detail.name)} - - } - value={issue.state} - onChange={(data: string) => { - partialUpdateIssue({ state: data }); - }} - maxHeight="md" - noChevron - disabled={isNotAllowed} - > - {states?.map((state) => ( - - <> - - {addSpaceIfCamelCase(state.name)} - - - ))} - + )} {properties.due_date && ( -
- - partialUpdateIssue({ - target_date: val, - }) - } - className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"} - /> -
-
Due date
-
{renderShortNumericDateFormat(issue.target_date ?? "")}
-
- {issue.target_date - ? issue.target_date < new Date().toISOString() - ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days` - : findHowManyDaysLeft(issue.target_date) <= 3 - ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days` - : "Due date" - : "N/A"} -
-
-
+ )} {properties.sub_issue_count && (
@@ -329,77 +188,12 @@ export const SingleListIssue: React.FC = ({
)} {properties.assignee && ( - { - const newData = issue.assignees ?? []; - - if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); - else newData.push(data); - - partialUpdateIssue({ assignees_list: newData }); - }} - className="group relative flex-shrink-0" - disabled={isNotAllowed} - > - {({ open }) => ( - <> -
- -
- -
-
- - - - {people?.map((person) => ( - - `flex items-center gap-x-1 cursor-pointer select-none p-2 ${ - active ? "bg-indigo-50" : "" - } ${ - selected || issue.assignees?.includes(person.member.id) - ? "bg-indigo-50 font-medium" - : "font-normal" - }` - } - value={person.member.id} - > - -

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

-
- ))} -
-
-
-
-
Assigned to
-
- {issue.assignee_details?.length > 0 - ? issue.assignee_details.map((assignee) => assignee.first_name).join(", ") - : "No one"} -
-
- - )} -
+ )} {type && !isNotAllowed && ( diff --git a/apps/app/components/core/list-view/single-list.tsx b/apps/app/components/core/list-view/single-list.tsx index 690da8175..b3d478593 100644 --- a/apps/app/components/core/list-view/single-list.tsx +++ b/apps/app/components/core/list-view/single-list.tsx @@ -95,31 +95,21 @@ export const SingleList: React.FC = ({
{groupedByIssues[groupTitle] ? ( groupedByIssues[groupTitle].length > 0 ? ( - groupedByIssues[groupTitle].map((issue: IIssue) => { - const assignees = [ - ...(issue?.assignees_list ?? []), - ...(issue?.assignees ?? []), - ]?.map((assignee) => { - const tempPerson = members?.find((p) => p.member.id === assignee)?.member; - - return tempPerson; - }); - - return ( - handleEditIssue(issue)} - handleDeleteIssue={handleDeleteIssue} - removeIssue={() => { - removeIssue && removeIssue(issue.bridge); - }} - userAuth={userAuth} - /> - ); - }) + groupedByIssues[groupTitle].map((issue: IIssue) => ( + handleEditIssue(issue)} + handleDeleteIssue={handleDeleteIssue} + removeIssue={() => { + removeIssue && removeIssue(issue.bridge); + }} + userAuth={userAuth} + /> + )) ) : (

No issues.

) diff --git a/apps/app/components/core/select/assignee.tsx b/apps/app/components/core/select/assignee.tsx new file mode 100644 index 000000000..ce5fe3774 --- /dev/null +++ b/apps/app/components/core/select/assignee.tsx @@ -0,0 +1,94 @@ +import React from "react"; + +// headless ui +import { Listbox, Transition } from "@headlessui/react"; +// ui +import { AssigneesList, Avatar } from "components/ui"; +// types +import { IIssue, IProjectMember } from "types"; + +type Props = { + issue: IIssue; + members: IProjectMember[] | undefined; + partialUpdateIssue: (formData: Partial) => void; + isNotAllowed: boolean; +}; + +export const AssigneeSelect: React.FC = ({ + issue, + members, + partialUpdateIssue, + isNotAllowed, +}) => ( + { + const newData = issue.assignees ?? []; + + if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); + else newData.push(data); + + partialUpdateIssue({ assignees_list: newData }); + }} + className="group relative flex-shrink-0" + disabled={isNotAllowed} + > + {({ open }) => ( + <> +
+ +
+ +
+
+ + + + {members?.map((member) => ( + + `flex items-center gap-x-1 cursor-pointer select-none p-2 ${ + 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} +

+
+ ))} +
+
+
+
+
Assigned to
+
+ {issue.assignee_details?.length > 0 + ? issue.assignee_details.map((assignee) => assignee.first_name).join(", ") + : "No one"} +
+
+ + )} +
+); diff --git a/apps/app/components/core/select/due-date.tsx b/apps/app/components/core/select/due-date.tsx new file mode 100644 index 000000000..dc71a8362 --- /dev/null +++ b/apps/app/components/core/select/due-date.tsx @@ -0,0 +1,48 @@ +// ui +import { CustomDatePicker } from "components/ui"; +// helpers +import { findHowManyDaysLeft, renderShortNumericDateFormat } from "helpers/date-time.helper"; +// types +import { IIssue } from "types"; + +type Props = { + issue: IIssue; + partialUpdateIssue: (formData: Partial) => void; + isNotAllowed: boolean; +}; + +export const DueDateSelect: React.FC = ({ issue, partialUpdateIssue, isNotAllowed }) => ( +
+ + partialUpdateIssue({ + target_date: val, + }) + } + className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"} + /> +
+
Due date
+
{renderShortNumericDateFormat(issue.target_date ?? "")}
+
+ {issue.target_date + ? issue.target_date < new Date().toISOString() + ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days` + : findHowManyDaysLeft(issue.target_date) <= 3 + ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days` + : "Due date" + : "N/A"} +
+
+
+); diff --git a/apps/app/components/core/select/index.ts b/apps/app/components/core/select/index.ts new file mode 100644 index 000000000..c5f427971 --- /dev/null +++ b/apps/app/components/core/select/index.ts @@ -0,0 +1,4 @@ +export * from "./assignee"; +export * from "./due-date"; +export * from "./priority"; +export * from "./state"; diff --git a/apps/app/components/core/select/priority.tsx b/apps/app/components/core/select/priority.tsx new file mode 100644 index 000000000..8c3110cbe --- /dev/null +++ b/apps/app/components/core/select/priority.tsx @@ -0,0 +1,97 @@ +import React from "react"; + +// ui +import { Listbox, Transition } from "@headlessui/react"; +// types +import { IIssue, IState } from "types"; +// constants +import { getPriorityIcon } from "constants/global"; +import { PRIORITIES } from "constants/"; + +type Props = { + issue: IIssue; + partialUpdateIssue: (formData: Partial) => void; + isNotAllowed: boolean; +}; + +export const PrioritySelect: React.FC = ({ issue, partialUpdateIssue, isNotAllowed }) => ( + { + partialUpdateIssue({ priority: data }); + }} + className="group relative flex-shrink-0" + disabled={isNotAllowed} + > + {({ open }) => ( + <> +
+ + {getPriorityIcon( + issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", + "text-sm" + )} + + + + + {PRIORITIES?.map((priority) => ( + + `flex cursor-pointer select-none items-center gap-x-2 px-3 py-2 capitalize ${ + active ? "bg-indigo-50" : "bg-white" + }` + } + value={priority} + > + {getPriorityIcon(priority, "text-sm")} + {priority ?? "None"} + + ))} + + +
+
+
Priority
+
+ {issue.priority ?? "None"} +
+
+ + )} +
+); diff --git a/apps/app/components/core/select/state.tsx b/apps/app/components/core/select/state.tsx new file mode 100644 index 000000000..d65d7e7d3 --- /dev/null +++ b/apps/app/components/core/select/state.tsx @@ -0,0 +1,55 @@ +// ui +import { CustomSelect } from "components/ui"; +// helpers +import { addSpaceIfCamelCase } from "helpers/string.helper"; +// types +import { IIssue, IState } from "types"; + +type Props = { + issue: IIssue; + states: IState[] | undefined; + partialUpdateIssue: (formData: Partial) => void; + isNotAllowed: boolean; +}; + +export const StateSelect: React.FC = ({ + issue, + states, + partialUpdateIssue, + isNotAllowed, +}) => ( + + s.id === issue.state)?.color, + }} + /> + {addSpaceIfCamelCase(states?.find((s) => s.id === issue.state)?.name ?? "")} + + } + value={issue.state} + onChange={(data: string) => { + partialUpdateIssue({ state: data }); + }} + maxHeight="md" + noChevron + disabled={isNotAllowed} + > + {states?.map((state) => ( + + <> + + {addSpaceIfCamelCase(state.name)} + + + ))} + +); diff --git a/apps/app/components/dnd/StrictModeDroppable.tsx b/apps/app/components/dnd/StrictModeDroppable.tsx index e63cab246..9ed01d3bf 100644 --- a/apps/app/components/dnd/StrictModeDroppable.tsx +++ b/apps/app/components/dnd/StrictModeDroppable.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; + // react beautiful dnd import { Droppable, DroppableProps } from "react-beautiful-dnd"; @@ -14,9 +15,7 @@ const StrictModeDroppable = ({ children, ...props }: DroppableProps) => { }; }, []); - if (!enabled) { - return null; - } + if (!enabled) return null; return {children}; }; diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index cb13b789e..bbc6552ba 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -17,7 +17,7 @@ import { Button } from "components/ui"; // types import type { CycleIssueResponse, IIssue, IssueResponse, ModuleIssueResponse } from "types"; // fetch-keys -import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, MODULE_ISSUES } from "constants/fetch-keys"; +import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, MODULE_ISSUES, USER_ISSUE } from "constants/fetch-keys"; type Props = { isOpen: boolean; @@ -30,7 +30,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) const [isDeleteLoading, setIsDeleteLoading] = useState(false); const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId: queryProjectId } = router.query; const { setToastAlert } = useToast(); @@ -46,10 +46,37 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) const handleDeletion = async () => { setIsDeleteLoading(true); if (!data || !workspaceSlug) return; + const projectId = data.project; await issueServices .deleteIssue(workspaceSlug as string, projectId, data.id) .then(() => { + const cycleId = data?.cycle; + const moduleId = data?.module; + + if (cycleId) { + mutate( + CYCLE_ISSUES(cycleId), + (prevData) => prevData?.filter((i) => i.issue !== data.id), + false + ); + } + + if (moduleId) { + mutate( + MODULE_ISSUES(moduleId), + (prevData) => prevData?.filter((i) => i.issue !== data.id), + false + ); + } + + if (!queryProjectId) + mutate( + USER_ISSUE(workspaceSlug as string), + (prevData) => prevData?.filter((i) => i.id !== data.id), + false + ); + mutate( PROJECT_ISSUES_LIST(workspaceSlug as string, projectId), (prevData) => ({ @@ -60,24 +87,6 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data }) false ); - const moduleId = data?.module; - const cycleId = data?.cycle; - - if (moduleId) { - mutate( - MODULE_ISSUES(moduleId), - (prevData) => prevData?.filter((i) => i.issue !== data.id), - false - ); - } - if (cycleId) { - mutate( - CYCLE_ISSUES(cycleId), - (prevData) => prevData?.filter((i) => i.issue !== data.id), - false - ); - } - handleClose(); setToastAlert({ title: "Success", diff --git a/apps/app/components/issues/my-issues-list-item.tsx b/apps/app/components/issues/my-issues-list-item.tsx index 2075c5161..8e7391283 100644 --- a/apps/app/components/issues/my-issues-list-item.tsx +++ b/apps/app/components/issues/my-issues-list-item.tsx @@ -1,33 +1,75 @@ -import React from "react"; +import React, { useCallback } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; +import useSWR, { mutate } from "swr"; + +// services +import stateService from "services/state.service"; +import issuesService from "services/issues.service"; // components +import { DueDateSelect, PrioritySelect, StateSelect } from "components/core/select"; +// ui import { AssigneesList } from "components/ui/avatar"; -// icons -import { CalendarDaysIcon } from "@heroicons/react/24/outline"; -// helpers -import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; +import { CustomMenu } from "components/ui"; // types import { IIssue, Properties } from "types"; -// constants -import { getPriorityIcon } from "constants/global"; +// fetch-keys +import { STATE_LIST, USER_ISSUE } from "constants/fetch-keys"; type Props = { - type?: string; issue: IIssue; properties: Properties; - editIssue?: () => void; - handleDeleteIssue?: () => void; - removeIssue?: () => void; + projectId: string; + handleDeleteIssue: () => void; }; -export const MyIssuesListItem: React.FC = ({ issue, properties }) => { +export const MyIssuesListItem: React.FC = ({ + issue, + properties, + projectId, + handleDeleteIssue, +}) => { const router = useRouter(); const { workspaceSlug } = router.query; + const { data: states } = useSWR( + workspaceSlug && projectId ? STATE_LIST(projectId) : null, + workspaceSlug && projectId + ? () => stateService.getStates(workspaceSlug as string, projectId) + : null + ); + + const partialUpdateIssue = useCallback( + (formData: Partial) => { + if (!workspaceSlug) return; + + mutate( + USER_ISSUE(workspaceSlug as string), + (prevData) => + prevData?.map((p) => { + if (p.id === issue.id) return { ...p, ...formData }; + + return p; + }), + false + ); + + issuesService + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData) + .then((res) => { + mutate(USER_ISSUE(workspaceSlug as string)); + }) + .catch((error) => { + console.log(error); + }); + }, + [workspaceSlug, projectId, issue] + ); + + const isNotAllowed = false; + return (
@@ -50,80 +92,26 @@ export const MyIssuesListItem: React.FC = ({ issue, properties }) => {
{properties.priority && ( -
- {getPriorityIcon(issue.priority)} -
-
Priority
-
- {issue.priority ?? "None"} -
-
-
+ )} {properties.state && ( -
- - {addSpaceIfCamelCase(issue?.state_detail.name)} -
-
State
-
{issue?.state_detail.name}
-
-
+ )} {properties.due_date && ( -
- - {issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"} -
-
Due date
-
{renderShortNumericDateFormat(issue.target_date ?? "")}
-
- {issue.target_date && - (issue.target_date < new Date().toISOString() - ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days` - : findHowManyDaysLeft(issue.target_date) <= 3 - ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days` - : "Due date")} -
-
-
+ )} {properties.sub_issue_count && (
@@ -135,6 +123,9 @@ export const MyIssuesListItem: React.FC = ({ issue, properties }) => {
)} + + Delete permanently +
); diff --git a/apps/app/components/issues/sidebar-select/cycle.tsx b/apps/app/components/issues/sidebar-select/cycle.tsx index 0c92a575f..353bc5121 100644 --- a/apps/app/components/issues/sidebar-select/cycle.tsx +++ b/apps/app/components/issues/sidebar-select/cycle.tsx @@ -75,7 +75,7 @@ export const SidebarCycleSelect: React.FC = ({ onChange={(value: any) => { value === null ? removeIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "") - : handleCycleChange(cycles?.find((c) => c.id === value) as any); + : handleCycleChange(cycles?.find((c) => c.id === value) as ICycle); }} disabled={isNotAllowed} > diff --git a/apps/app/components/issues/sidebar-select/module.tsx b/apps/app/components/issues/sidebar-select/module.tsx index df0a3d050..e57688887 100644 --- a/apps/app/components/issues/sidebar-select/module.tsx +++ b/apps/app/components/issues/sidebar-select/module.tsx @@ -2,28 +2,32 @@ import React from "react"; import { useRouter } from "next/router"; -import useSWR from "swr"; +import useSWR, { mutate } from "swr"; -import { Control, Controller } from "react-hook-form"; -// constants -import { RectangleGroupIcon } from "@heroicons/react/24/outline"; // services import modulesService from "services/modules.service"; // ui import { Spinner, CustomSelect } from "components/ui"; // icons +import { RectangleGroupIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, IModule } from "types"; -import { MODULE_LIST } from "constants/fetch-keys"; +import { IIssue, IModule, UserAuth } from "types"; +// fetch-keys +import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys"; type Props = { - control: Control; + issueDetail: IIssue | undefined; handleModuleChange: (module: IModule) => void; + userAuth: UserAuth; }; -export const SidebarModuleSelect: React.FC = ({ control, handleModuleChange }) => { +export const SidebarModuleSelect: React.FC = ({ + issueDetail, + handleModuleChange, + userAuth, +}) => { const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, issueId } = router.query; const { data: modules } = useSWR( workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null, @@ -32,46 +36,67 @@ export const SidebarModuleSelect: React.FC = ({ control, handleModuleChan : null ); + const removeIssueFromModule = (bridgeId: string, moduleId: string) => { + if (!workspaceSlug || !projectId) return; + + modulesService + .removeIssueFromModule(workspaceSlug as string, projectId as string, moduleId, bridgeId) + .then((res) => { + mutate(ISSUE_DETAILS(issueId as string)); + + mutate(MODULE_ISSUES(moduleId)); + }) + .catch((e) => { + console.log(e); + }); + }; + + const issueModule = issueDetail?.issue_module; + + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + return (

Module

-
- ( - - {value ? modules?.find((m) => m.id === value?.module_detail.id)?.name : "None"} - - } - value={value} - onChange={(value: any) => { - handleModuleChange(modules?.find((m) => m.id === value) as any); - }} +
+ - {modules ? ( - modules.length > 0 ? ( - modules.map((option) => ( - - {option.name} - - )) - ) : ( -
No cycles found
- ) - ) : ( - - )} -
+ {modules?.find((m) => m.id === issueModule?.module)?.name ?? "None"} + + } + value={issueModule?.module_detail?.id} + onChange={(value: any) => { + value === null + ? removeIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "") + : handleModuleChange(modules?.find((m) => m.id === value) as IModule); + }} + disabled={isNotAllowed} + > + {modules ? ( + modules.length > 0 ? ( + <> + + None + + {modules.map((option) => ( + + {option.name} + + ))} + + ) : ( +
No modules found
+ ) + ) : ( + )} - /> +
); diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index 5f023e41e..54f94f6ab 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -14,6 +14,7 @@ import { Popover, Listbox, Transition } from "@headlessui/react"; import useToast from "hooks/use-toast"; // services import issuesServices from "services/issues.service"; +import modulesService from "services/modules.service"; // components import { DeleteIssueModal, @@ -21,6 +22,7 @@ import { SidebarBlockedSelect, SidebarBlockerSelect, SidebarCycleSelect, + SidebarModuleSelect, SidebarParentSelect, SidebarPrioritySelect, SidebarStateSelect, @@ -40,7 +42,7 @@ import { // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types -import type { ICycle, IIssue, IIssueLabels, UserAuth } from "types"; +import type { ICycle, IIssue, IIssueLabels, IModule, UserAuth } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS, PROJECT_ISSUES_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; @@ -123,6 +125,18 @@ export const IssueDetailsSidebar: React.FC = ({ }); }; + const handleModuleChange = (moduleDetail: IModule) => { + if (!workspaceSlug || !projectId || !issueDetail) return; + + modulesService + .addIssuesToModule(workspaceSlug as string, projectId as string, moduleDetail.id, { + issues: [issueDetail.id], + }) + .then((res) => { + mutate(ISSUE_DETAILS(issueId as string)); + }); + }; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -263,6 +277,11 @@ export const IssueDetailsSidebar: React.FC = ({ handleCycleChange={handleCycleChange} userAuth={userAuth} /> +
diff --git a/apps/app/constants/index.ts b/apps/app/constants/index.ts index 49641dc1e..dd2ed499b 100644 --- a/apps/app/constants/index.ts +++ b/apps/app/constants/index.ts @@ -35,7 +35,8 @@ export const groupByOptions: Array<{ name: string; key: NestedKeyOf | nu { name: "None", key: null }, ]; -export const orderByOptions: Array<{ name: string; key: NestedKeyOf | null }> = [ +export const orderByOptions: Array<{ name: string; key: NestedKeyOf | "manual" | null }> = [ + // { name: "Manual", key: "manual" }, { name: "Last created", key: "created_at" }, { name: "Last updated", key: "updated_at" }, { name: "Priority", key: "priority" }, diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index 149c1c31b..c2c655470 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -19,7 +19,7 @@ type IssueViewProps = { issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; - orderBy: NestedKeyOf | null; + orderBy: NestedKeyOf | "manual" | null; }; type ReducerActionType = { @@ -34,12 +34,12 @@ type ReducerActionType = { }; type ContextType = { - orderBy: NestedKeyOf | null; + orderBy: NestedKeyOf | "manual" | null; issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; setGroupByProperty: (property: NestedKeyOf | null) => void; - setOrderBy: (property: NestedKeyOf | null) => void; + setOrderBy: (property: NestedKeyOf | "manual" | null) => void; setFilterIssue: (property: "activeIssue" | "backlogIssue" | null) => void; resetFilterToDefault: () => void; setNewFilterDefaultView: () => void; @@ -51,7 +51,7 @@ type StateType = { issueView: "list" | "kanban" | null; groupByProperty: NestedKeyOf | null; filterIssue: "activeIssue" | "backlogIssue" | null; - orderBy: NestedKeyOf | null; + orderBy: NestedKeyOf | "manual" | null; }; type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; @@ -219,7 +219,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = ); const setOrderBy = useCallback( - (property: NestedKeyOf | null) => { + (property: NestedKeyOf | "manual" | null) => { dispatch({ type: "SET_ORDER_BY_PROPERTY", payload: { diff --git a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx index 943f4028f..363ee0938 100644 --- a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx +++ b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; // headless ui @@ -17,178 +17,194 @@ import useIssuesProperties from "hooks/use-issue-properties"; // types import { IIssue, Properties } from "types"; // components -import { MyIssuesListItem } from "components/issues"; +import { DeleteIssueModal, MyIssuesListItem } from "components/issues"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; // types import type { NextPage } from "next"; const MyIssuesPage: NextPage = () => { + const [deleteIssueModal, setDeleteIssueModal] = useState(false); + const [issueToDelete, setIssueToDelete] = useState(null); + const router = useRouter(); const { workspaceSlug } = router.query; // fetching user issues - const { myIssues } = useIssues(workspaceSlug?.toString()); + const { myIssues } = useIssues(workspaceSlug as string); - // FIXME: remove this hard-coded value const [properties, setProperties] = useIssuesProperties( workspaceSlug ? (workspaceSlug as string) : undefined, undefined ); + const handleDeleteIssue = (issue: IIssue) => { + setDeleteIssueModal(true); + setIssueToDelete(issue); + }; + return ( - - - - } - right={ -
- - {({ open }) => ( - <> - - View - + <> + setDeleteIssueModal(false)} + isOpen={deleteIssueModal} + data={issueToDelete} + /> + + + + } + right={ +
+ + {({ open }) => ( + <> + + View + - - -
-
-

Properties

-
- {Object.keys(properties).map((key) => ( - - ))} -
-
-
-
-
- - )} -
- { - const e = new KeyboardEvent("keydown", { - key: "c", - }); - - document.dispatchEvent(e); - }} - /> -
- } - > -
- {myIssues ? ( - <> - {myIssues.length > 0 ? ( -
- - {({ open }) => ( -
-
- -
- - + +
+
+

Properties

+
+ {Object.keys(properties).map((key) => ( +
- -
- - -
- {myIssues.map((issue: IIssue) => ( - + onClick={() => setProperties(key as keyof Properties)} + > + {replaceUnderscoreIfSnakeCase(key)} + ))}
-
-
-
- )} - -
- ) : ( -
- - - Use
C
shortcut - to create a new issue - - } - Icon={PlusIcon} - action={() => { - const e = new KeyboardEvent("keydown", { - key: "c", - }); - document.dispatchEvent(e); - }} - /> -
-
- )} - - ) : ( -
- +
+
+ + + + )} + + { + const e = new KeyboardEvent("keydown", { + key: "c", + }); + + document.dispatchEvent(e); + }} + />
- )} -
- + } + > +
+ {myIssues ? ( + <> + {myIssues.length > 0 ? ( +
+ + {({ open }) => ( +
+
+ +
+ + + +

My Issues

+

{myIssues.length}

+
+
+
+ + +
+ {myIssues.map((issue: IIssue) => ( + handleDeleteIssue(issue)} + /> + ))} +
+
+
+
+ )} +
+
+ ) : ( +
+ + + Use
C
shortcut + to create a new issue + + } + Icon={PlusIcon} + action={() => { + const e = new KeyboardEvent("keydown", { + key: "c", + }); + document.dispatchEvent(e); + }} + /> +
+
+ )} + + ) : ( +
+ +
+ )} +
+ + ); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index cbd3eef69..895723361 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -50,6 +50,7 @@ const defaultValues = { blocked_list: [], target_date: new Date().toString(), issue_cycle: null, + issue_module: null, labels_list: [], }; diff --git a/apps/app/pages/_app.tsx b/apps/app/pages/_app.tsx index d838868a6..f10a6f722 100644 --- a/apps/app/pages/_app.tsx +++ b/apps/app/pages/_app.tsx @@ -10,15 +10,13 @@ import type { AppProps } from "next/app"; function MyApp({ Component, pageProps }: AppProps) { return ( - <> - - - - - - - - + + + + + + + ); }