From b1aa3899305dd197ee77c3a6dbcdb997f9a9c815 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 8 Dec 2022 04:57:10 +0530 Subject: [PATCH] style: issue details, list view, kanban card --- apps/app/components/command-palette/index.tsx | 11 +- .../components/project/CreateProjectModal.tsx | 2 +- .../project/issues/BoardView/SingleBoard.tsx | 232 +++--- .../SelectParentIssue.tsx | 2 +- .../issues/CreateUpdateIssueModal/index.tsx | 22 +- .../project/issues/IssuesListModal.tsx | 77 +- .../project/issues/ListView/index.tsx | 691 +++++++++--------- .../project/issues/PreviewModal/index.tsx | 138 ---- .../issue-detail/IssueDetailSidebar.tsx | 174 +++-- .../issue-detail/comment/IssueCommentCard.tsx | 18 +- .../comment/IssueCommentSection.tsx | 2 +- .../project/settings/GeneralSettings.tsx | 10 +- .../project/settings/LabelsSettings.tsx | 35 +- apps/app/constants/global.tsx | 12 + .../{AdminLayout.tsx => AppLayout.tsx} | 12 +- apps/app/layouts/types.d.ts | 2 + apps/app/lib/hooks/useIssuesProperties.tsx | 2 +- apps/app/pages/me/my-issues.tsx | 6 +- apps/app/pages/me/profile.tsx | 6 +- apps/app/pages/me/workspace-invites.tsx | 6 +- .../app/pages/projects/[projectId]/cycles.tsx | 6 +- .../projects/[projectId]/issues/[issueId].tsx | 102 +-- .../projects/[projectId]/issues/index.tsx | 152 ++-- .../pages/projects/[projectId]/members.tsx | 6 +- .../pages/projects/[projectId]/settings.tsx | 496 ++----------- apps/app/pages/projects/index.tsx | 6 +- apps/app/pages/workspace/index.tsx | 6 +- apps/app/pages/workspace/members.tsx | 8 +- apps/app/pages/workspace/settings.tsx | 6 +- apps/app/styles/globals.css | 2 +- apps/app/types/issues.d.ts | 4 +- apps/app/ui/EmptySpace/index.tsx | 7 +- 32 files changed, 935 insertions(+), 1326 deletions(-) delete mode 100644 apps/app/components/project/issues/PreviewModal/index.tsx create mode 100644 apps/app/constants/global.tsx rename apps/app/layouts/{AdminLayout.tsx => AppLayout.tsx} (69%) diff --git a/apps/app/components/command-palette/index.tsx b/apps/app/components/command-palette/index.tsx index 1b5694616..63490c97d 100644 --- a/apps/app/components/command-palette/index.tsx +++ b/apps/app/components/command-palette/index.tsx @@ -278,15 +278,15 @@ const CommandPalette: React.FC = () => { value={issue.id} /> - + {activeProject?.identifier}-{issue.sequence_id} - {issue.name} + {issue.name} {active && ( )} diff --git a/apps/app/components/project/CreateProjectModal.tsx b/apps/app/components/project/CreateProjectModal.tsx index 6402829dc..c93f9123b 100644 --- a/apps/app/components/project/CreateProjectModal.tsx +++ b/apps/app/components/project/CreateProjectModal.tsx @@ -44,7 +44,7 @@ const IsGuestCondition: React.FC<{ type: "error", message: "You don't have permission to create project.", }); - }, []); + }, [setIsOpen, setToastAlert]); return null; }; diff --git a/apps/app/components/project/issues/BoardView/SingleBoard.tsx b/apps/app/components/project/issues/BoardView/SingleBoard.tsx index 6a55a761d..2c0426e93 100644 --- a/apps/app/components/project/issues/BoardView/SingleBoard.tsx +++ b/apps/app/components/project/issues/BoardView/SingleBoard.tsx @@ -18,11 +18,10 @@ import { ArrowsPointingOutIcon, CalendarDaysIcon, EllipsisHorizontalIcon, - PencilIcon, PlusIcon, } from "@heroicons/react/24/outline"; import Image from "next/image"; -import { divide } from "lodash"; +import { getPriorityIcon } from "constants/global"; type Props = { selectedGroup: NestedKeyOf | null; @@ -181,133 +180,116 @@ const SingleBoard: React.FC = ({ ref={provided.innerRef} {...provided.draggableProps} > -
- +
+ {properties.key && ( +
+ {childIssue.project_detail?.identifier}-{childIssue.sequence_id} +
+ )} +
{childIssue.name} - - {Object.keys(properties).map( - (key) => - properties[key as keyof Properties] && - !Array.isArray(childIssue[key as keyof IIssue]) && ( -
- {key === "start_date" && childIssue.start_date !== null && ( - - - {renderShortNumericDateFormat(childIssue.start_date)} - - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "None"} - - )} - {key === "due_date" && ( - <> - +
+ {properties.priority && ( +
+ {/* {getPriorityIcon(childIssue.priority ?? "")} */} + {childIssue.priority} +
+ )} + {properties.state && ( +
+ + {addSpaceIfCamelCase(childIssue.state_detail.name)} +
+ )} + {properties.start_date && ( +
+ + {childIssue.start_date + ? renderShortNumericDateFormat(childIssue.start_date) + : "N/A"} +
+ )} + {properties.target_date && ( +
+ + {childIssue.target_date + ? renderShortNumericDateFormat(childIssue.target_date) + : "N/A"} + {childIssue.target_date && ( + + {childIssue.target_date < new Date().toISOString() + ? `Target date has passed by ${findHowManyDaysLeft( + childIssue.target_date + )} days` + : findHowManyDaysLeft(childIssue.target_date) <= 3 + ? `Target date is in ${findHowManyDaysLeft( + childIssue.target_date + )} days` + : "Target date"} + + )} +
+ )} + {properties.assignee && ( +
+ {childIssue?.assignee_details?.length > 0 ? ( + childIssue?.assignee_details?.map( + (assignee, index: number) => ( +
- - {childIssue.target_date - ? renderShortNumericDateFormat(childIssue.target_date) - : "N/A"} - {childIssue.target_date && ( - - {childIssue.target_date < new Date().toISOString() - ? `Due date has passed by ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : findHowManyDaysLeft(childIssue.target_date) <= 3 - ? `Due date is in ${findHowManyDaysLeft( - childIssue.target_date - )} days` - : "Due date"} - + {assignee.avatar && assignee.avatar !== "" ? ( +
+ {assignee.name} +
+ ) : ( +
+ {assignee.first_name.charAt(0)} +
)} - - - )} - {key === "key" && ( - - {childIssue.project_detail?.identifier}- - {childIssue.sequence_id} - - )} - {key === "state" && ( - <>{addSpaceIfCamelCase(childIssue["state_detail"].name)} - )} - {key === "priority" && <>{childIssue.priority}} - {/* {key === "description" && <>{childIssue.description}} */} - {key === "assignee" ? ( -
- {childIssue?.assignee_details?.length > 0 ? ( - childIssue?.assignee_details?.map( - (assignee, index: number) => ( -
- {assignee.avatar && assignee.avatar !== "" ? ( -
- {assignee.name} -
- ) : ( -
- {assignee.first_name.charAt(0)} -
- )} -
- ) - ) - ) : ( - No assignee. - )} -
- ) : null} -
- ) - )} +
+ ) + ) + ) : ( + No assignee. + )} +
+ )} +
diff --git a/apps/app/components/project/issues/CreateUpdateIssueModal/SelectParentIssue.tsx b/apps/app/components/project/issues/CreateUpdateIssueModal/SelectParentIssue.tsx index e8f1648e8..d6b463ffb 100644 --- a/apps/app/components/project/issues/CreateUpdateIssueModal/SelectParentIssue.tsx +++ b/apps/app/components/project/issues/CreateUpdateIssueModal/SelectParentIssue.tsx @@ -14,7 +14,7 @@ type Props = { control: Control; isOpen: boolean; setIsOpen: React.Dispatch>; - issues: IssueResponse | undefined; + issues: IIssue[]; }; const SelectParent: React.FC = ({ control, isOpen, setIsOpen, issues }) => { diff --git a/apps/app/components/project/issues/CreateUpdateIssueModal/index.tsx b/apps/app/components/project/issues/CreateUpdateIssueModal/index.tsx index 1c0fc9d46..b7559d988 100644 --- a/apps/app/components/project/issues/CreateUpdateIssueModal/index.tsx +++ b/apps/app/components/project/issues/CreateUpdateIssueModal/index.tsx @@ -392,16 +392,16 @@ const CreateUpdateIssuesModal: React.FC = ({ /> */}
- + /> */}
@@ -409,11 +409,25 @@ const CreateUpdateIssuesModal: React.FC = ({ + ( + { + onChange(e.target.value); + }} + className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300" + /> + )} + /> diff --git a/apps/app/components/project/issues/IssuesListModal.tsx b/apps/app/components/project/issues/IssuesListModal.tsx index 701da9beb..aba9a0c34 100644 --- a/apps/app/components/project/issues/IssuesListModal.tsx +++ b/apps/app/components/project/issues/IssuesListModal.tsx @@ -2,35 +2,49 @@ import React, { useState } from "react"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; +// ui +import { Button } from "ui"; // icons import { MagnifyingGlassIcon, RectangleStackIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, IssueResponse } from "types"; +import { IIssue } from "types"; import { classNames } from "constants/common"; import useUser from "lib/hooks/useUser"; type Props = { isOpen: boolean; handleClose: () => void; + value?: any; onChange: (...event: any[]) => void; - issues: IssueResponse | undefined; + issues: IIssue[]; + title?: string; + multiple?: boolean; }; -const IssuesListModal: React.FC = ({ isOpen, handleClose: onClose, onChange, issues }) => { +const IssuesListModal: React.FC = ({ + isOpen, + handleClose: onClose, + value, + onChange, + issues, + title = "Issues", + multiple = false, +}) => { const [query, setQuery] = useState(""); + const [values, setValues] = useState([]); const { activeProject } = useUser(); const handleClose = () => { onClose(); setQuery(""); + setValues([]); }; const filteredIssues: IIssue[] = query === "" - ? issues?.results ?? [] - : issues?.results.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? - []; + ? issues ?? [] + : issues?.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? []; return ( <> @@ -59,7 +73,14 @@ const IssuesListModal: React.FC = ({ isOpen, handleClose: onClose, onChan leaveTo="opacity-0 scale-95" > - + { + if (multiple) setValues(val); + else onChange(val); + }} + // multiple={multiple} + >
= ({ isOpen, handleClose: onClose, onChan
  • {query === "" && (

    - Issues + {title}

    )}
      @@ -95,20 +116,26 @@ const IssuesListModal: React.FC = ({ isOpen, handleClose: onClose, onChan ) } onClick={() => { - // setIssueIdFromList(issue.id); - handleClose(); + if (!multiple) handleClose(); }} > - - - {activeProject?.identifier}-{issue.sequence_id} - {" "} - {issue.name} + {({ selected }) => ( + <> + {multiple ? ( + + ) : null} + + + {activeProject?.identifier}-{issue.sequence_id} + {" "} + {issue.name} + + )} ))}
    @@ -128,6 +155,16 @@ const IssuesListModal: React.FC = ({ isOpen, handleClose: onClose, onChan
  • )}
    + {multiple ? ( +
    + + +
    + ) : null}
    diff --git a/apps/app/components/project/issues/ListView/index.tsx b/apps/app/components/project/issues/ListView/index.tsx index 625fd0689..c4a7e19f1 100644 --- a/apps/app/components/project/issues/ListView/index.tsx +++ b/apps/app/components/project/issues/ListView/index.tsx @@ -1,5 +1,5 @@ // react -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; // next import Link from "next/link"; import Image from "next/image"; @@ -10,7 +10,7 @@ import { Listbox, Transition } from "@headlessui/react"; // icons import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, IssueResponse, IState, NestedKeyOf, Properties, WorkspaceMember } from "types"; +import { IIssue, IssueResponse, NestedKeyOf, Properties, WorkspaceMember } from "types"; // hooks import useUser from "lib/hooks/useUser"; // fetch keys @@ -20,13 +20,7 @@ import { PROJECT_ISSUES_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys"; import issuesServices from "lib/services/issues.services"; import workspaceService from "lib/services/workspace.service"; // constants -import { - addSpaceIfCamelCase, - classNames, - renderShortNumericDateFormat, - replaceUnderscoreIfSnakeCase, -} from "constants/common"; -import IssuePreviewModal from "../PreviewModal"; +import { addSpaceIfCamelCase, classNames, renderShortNumericDateFormat } from "constants/common"; // types type Props = { @@ -44,9 +38,6 @@ const ListView: React.FC = ({ setSelectedIssue, handleDeleteIssue, }) => { - const [issuePreviewModal, setIssuePreviewModal] = useState(false); - const [previewModalIssueId, setPreviewModalIssueId] = useState(null); - const { activeWorkspace, activeProject, states } = useUser(); const partialUpdateIssue = (formData: Partial, issueId: string) => { @@ -74,368 +65,348 @@ const ListView: React.FC = ({ activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null ); - const handleHover = (issueId: string) => { - document.addEventListener("keydown", (e) => { - // if (e.code === "Space") { - // e.preventDefault(); - // setPreviewModalIssueId(issueId); - // setIssuePreviewModal(true); - // } - }); - }; - return ( -
    - -
    -
    -
    - - - - - {Object.keys(properties).map( - (key) => - properties[key as keyof Properties] && ( - - ) - )} - - - - - {Object.keys(groupedByIssues).map((singleGroup) => ( - - {selectedGroup !== null ? ( - - + ) : ( + + )} + + ) + )} + + + ); + }) + : null} + +
    - NAME - - {replaceUnderscoreIfSnakeCase(key)} - - ACTIONS -
    +
    + {Object.keys(groupedByIssues).map((singleGroup) => ( +
    +
    +
    + + {selectedGroup !== null ? ( + + + - - ) : null} - {groupedByIssues[singleGroup].length > 0 - ? groupedByIssues[singleGroup].map((issue: IIssue, index: number) => { - const assignees = [ - ...(issue?.assignees_list ?? []), - ...(issue?.assignees ?? []), - ]?.map( - (assignee) => - people?.find((p) => p.member.id === assignee)?.member.email - ); + + + + + ) : ( + + + + + + )} + + {groupedByIssues[singleGroup].length > 0 + ? groupedByIssues[singleGroup].map((issue: IIssue, index: number) => { + const assignees = [ + ...(issue?.assignees_list ?? []), + ...(issue?.assignees ?? []), + ]?.map( + (assignee) => people?.find((p) => p.member.id === assignee)?.member.email + ); - return ( - handleHover(issue.id)} - > - - {Object.keys(properties).map( - (key) => - properties[key as keyof Properties] && ( - - {(key as keyof Properties) === "key" ? ( - - ) : (key as keyof Properties) === "priority" ? ( - + {Object.keys(properties).map( + (key) => + properties[key as keyof Properties] && ( + + {(key as keyof Properties) === "key" ? ( + + ) : (key as keyof Properties) === "priority" ? ( + + ) : (key as keyof Properties) === "assignee" ? ( + - ) : (key as keyof Properties) === "assignee" ? ( - + ) : (key as keyof Properties) === "state" ? ( + - ) : (key as keyof Properties) === "state" ? ( - - ) : (key as keyof Properties) === "due_date" ? ( - - ) : ( - - )} - - ) - )} - - - ); - }) - : null} - - ))} - -
    +
    + {selectedGroup === "state_detail.name" ? ( + s.name === singleGroup)?.color, + }} + > + ) : null} {singleGroup === null || singleGroup === "null" ? selectedGroup === "priority" && "No priority" : addSpaceIfCamelCase(singleGroup)} {groupedByIssues[singleGroup as keyof IIssue].length} -
    + ALL ISSUES + + {groupedByIssues[singleGroup as keyof IIssue].length} + +
    - - {issue.name} - - - {activeProject?.identifier}-{issue.sequence_id} - - { - partialUpdateIssue({ priority: data }, issue.id); - }} - className="flex-shrink-0" - > - {({ open }) => ( - <> -
    - - - {issue.priority ?? "None"} - - - - +
    + + {issue.name} + + + {activeProject?.identifier}-{issue.sequence_id} + + { + partialUpdateIssue({ priority: data }, issue.id); + }} + className="flex-shrink-0" + > + {({ open }) => ( + <> +
    + + - - {PRIORITIES?.map((priority) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer capitalize select-none px-3 py-2" + {issue.priority ?? "None"} + + + + + + {PRIORITIES?.map((priority) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer capitalize select-none px-3 py-2" + ) + } + value={priority} + > + {priority} + + ))} + + +
    + + )} +
    +
    + { + const newData = issue.assignees ?? []; + if (newData.includes(data)) { + newData.splice(newData.indexOf(data), 1); + } else { + newData.push(data); + } + partialUpdateIssue( + { assignees_list: newData }, + issue.id + ); + }} + className="flex-shrink-0" + > + {({ open }) => ( + <> +
    + + {() => { + if (assignees.length > 0) + return ( + <> + {assignees.map((assignee, index) => ( +
    + {assignee} +
    + ))} + + ); + else return None; + }} +
    + + + + {people?.map((person) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none px-3 py-2" + ) + } + value={person.member.id} + > +
    - {priority} - - ))} - - -
    - - )} - -
    - { - const newData = issue.assignees ?? []; - if (newData.includes(data)) { - newData.splice(newData.indexOf(data), 1); - } else { - newData.push(data); - } - partialUpdateIssue( - { assignees_list: newData }, - issue.id - ); - }} - className="flex-shrink-0" - > - {({ open }) => ( - <> -
    - - {() => { - if (assignees.length > 0) - return ( - <> - {assignees.map((assignee, index) => ( -
    - {assignee} -
    - ))} - - ); - else return None; - }} -
    + {person.member.avatar && + person.member.avatar !== "" ? ( +
    + avatar +
    + ) : ( +

    + {person.member.first_name.charAt(0)} +

    + )} +

    {person.member.first_name}

    +
    + + ))} + + + + + )} +
    +
    + { + partialUpdateIssue({ state: data }, issue.id); + }} + className="flex-shrink-0" + > + {({ open }) => ( + <> +
    + + + {addSpaceIfCamelCase(issue.state_detail.name)} + + - - - {people?.map((person) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer select-none px-3 py-2" - ) - } - value={person.member.id} - > -
    - {person.member.avatar && - person.member.avatar !== "" ? ( -
    - avatar -
    - ) : ( -

    - {person.member.first_name.charAt(0)} -

    - )} -

    {person.member.first_name}

    -
    -
    - ))} -
    -
    -
    - - )} -
    -
    - { - partialUpdateIssue({ state: data }, issue.id); - }} - className="flex-shrink-0" - > - {({ open }) => ( - <> -
    - - - {addSpaceIfCamelCase(issue.state_detail.name)} - - - - - - {states?.map((state) => ( - - classNames( - active ? "bg-indigo-50" : "bg-white", - "cursor-pointer select-none px-3 py-2" - ) - } - value={state.id} - > - {addSpaceIfCamelCase(state.name)} - - ))} - - -
    - - )} -
    -
    - {issue.target_date - ? renderShortNumericDateFormat(issue.target_date) - : "-"} - - {issue[key as keyof IIssue] ?? - (issue[key as keyof IIssue] as any)?.name ?? - "None"} - -
    - - -
    -
    + + + {states?.map((state) => ( + + classNames( + active ? "bg-indigo-50" : "bg-white", + "cursor-pointer select-none px-3 py-2" + ) + } + value={state.id} + > + {addSpaceIfCamelCase(state.name)} + + ))} + + +
    + + )} + + + ) : (key as keyof Properties) === "target_date" ? ( +
    + {issue.target_date + ? renderShortNumericDateFormat(issue.target_date) + : "-"} + + {issue[key as keyof IIssue] ?? + (issue[key as keyof IIssue] as any)?.name ?? + "None"} + +
    + + +
    +
    +
    -
    + ))} ); }; diff --git a/apps/app/components/project/issues/PreviewModal/index.tsx b/apps/app/components/project/issues/PreviewModal/index.tsx deleted file mode 100644 index b1c9b8e0a..000000000 --- a/apps/app/components/project/issues/PreviewModal/index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -// next -import { useRouter } from "next/router"; -// react -import { Fragment } from "react"; -// headless ui -import { Dialog, Transition } from "@headlessui/react"; -// hooks -import useUser from "lib/hooks/useUser"; -// services -import issuesServices from "lib/services/issues.services"; -import projectService from "lib/services/project.service"; -// swr -import useSWR from "swr"; -// types -import { IIssue, ProjectMember } from "types"; -// constants -import { PROJECT_ISSUES_DETAILS, PROJECT_MEMBERS } from "constants/fetch-keys"; -import { Button } from "ui"; -import { ChartBarIcon, Squares2X2Icon, TagIcon, UserIcon } from "@heroicons/react/24/outline"; - -type Props = { - isOpen: boolean; - setIsOpen: React.Dispatch>; - issueId: string | null; -}; - -const IssuePreviewModal = ({ isOpen, setIsOpen, issueId }: Props) => { - const closeModal = () => { - setIsOpen(false); - }; - - const { activeWorkspace, activeProject } = useUser(); - - const router = useRouter(); - - const { data: issueDetails } = useSWR( - activeWorkspace && activeProject && issueId ? PROJECT_ISSUES_DETAILS(issueId) : null, - activeWorkspace && activeProject && issueId - ? () => issuesServices.getIssue(activeWorkspace.slug, activeProject.id, issueId) - : null - ); - - const { data: users } = useSWR( - activeWorkspace && activeProject ? PROJECT_MEMBERS(activeProject.id) : null, - activeWorkspace && activeProject - ? () => projectService.projectMembers(activeWorkspace.slug, activeProject.id) - : null - ); - - return ( - <> - - - -
    - - -
    -
    - - - - {issueDetails?.project_detail.identifier}-{issueDetails?.sequence_id}{" "} - {issueDetails?.name} - - Created by{" "} - {users?.find((u) => u.id === issueDetails?.created_by)?.member.first_name} - - -
    -

    {issueDetails?.description}

    -
    -
    - - - {issueDetails?.state_detail.name} - - - - {issueDetails?.priority} - - - - {issueDetails?.label_details && issueDetails.label_details.length > 0 - ? issueDetails.label_details.map((label) => ( - {label.name} - )) - : "None"} - - - - {issueDetails?.assignee_details && issueDetails.assignee_details.length > 0 - ? issueDetails.assignee_details.map((assignee) => ( - {assignee.first_name} - )) - : "None"} - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - - ); -}; - -export default IssuePreviewModal; diff --git a/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx b/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx index 9516c4238..12f0773f2 100644 --- a/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx +++ b/apps/app/components/project/issues/issue-detail/IssueDetailSidebar.tsx @@ -4,13 +4,14 @@ import useSWR from "swr"; // headless ui import { Listbox, Transition } from "@headlessui/react"; // react hook form -import { useForm, Controller } from "react-hook-form"; +import { useForm, Controller, UseFormWatch } from "react-hook-form"; // services import stateServices from "lib/services/state.services"; import issuesServices from "lib/services/issues.services"; import workspaceService from "lib/services/workspace.service"; // hooks import useUser from "lib/hooks/useUser"; +import useToast from "lib/hooks/useToast"; // fetching keys import { PROJECT_ISSUES_LIST, @@ -20,6 +21,7 @@ import { } from "constants/fetch-keys"; // commons import { classNames, copyTextToClipboard } from "constants/common"; +import { PRIORITIES } from "constants/"; // ui import { Input, Button, Spinner } from "ui"; import { Popover } from "@headlessui/react"; @@ -38,25 +40,40 @@ import { } from "@heroicons/react/24/outline"; // types import type { Control } from "react-hook-form"; -import type { IIssue, IIssueLabels, IssueResponse, IState, WorkspaceMember } from "types"; +import type { + IIssue, + IIssueLabels, + IssueResponse, + IState, + NestedKeyOf, + WorkspaceMember, +} from "types"; import { TwitterPicker } from "react-color"; -import useToast from "lib/hooks/useToast"; +import IssuesListModal from "components/project/issues/IssuesListModal"; type Props = { control: Control; submitChanges: (formData: Partial) => void; issueDetail: IIssue | undefined; + watch: UseFormWatch; }; -const PRIORITIES = ["high", "medium", "low"]; - const defaultValues: Partial = { name: "", colour: "#ff0000", }; -const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDetail }) => { - const { activeWorkspace, activeProject, cycles } = useUser(); +const IssueDetailSidebar: React.FC = ({ + control, + watch: watchIssue, + submitChanges, + issueDetail, +}) => { + const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false); + const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); + const [isParentModalOpen, setIsParentModalOpen] = useState(false); + + const { activeWorkspace, activeProject, cycles, issues } = useUser(); const { setToastAlert } = useToast(); @@ -72,15 +89,6 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta activeWorkspace ? () => workspaceService.workspaceMembers(activeWorkspace.slug) : null ); - const { data: projectIssues } = useSWR( - activeProject && activeWorkspace - ? PROJECT_ISSUES_LIST(activeWorkspace.slug, activeProject.id) - : null, - activeProject && activeWorkspace - ? () => issuesServices.getIssues(activeWorkspace.slug, activeProject.id) - : null - ); - const { data: issueLabels, mutate: issueLabelMutate } = useSWR( activeProject && activeWorkspace ? PROJECT_ISSUE_LABELS(activeProject.id) : null, activeProject && activeWorkspace @@ -110,7 +118,19 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta }); }; - const sidebarSections = [ + const sidebarSections: Array< + Array<{ + label: string; + name: NestedKeyOf; + canSelectMultipleOptions: boolean; + icon: (props: any) => JSX.Element; + options?: Array<{ label: string; value: any }>; + modal: boolean; + issuesList?: Array; + isOpen?: boolean; + setIsOpen?: (arg: boolean) => void; + }> + > = [ [ { label: "Status", @@ -121,6 +141,7 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta label: state.name, value: state.id, })), + modal: false, }, { label: "Assignees", @@ -131,6 +152,7 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta label: person.member.first_name, value: person.member.id, })), + modal: false, }, { label: "Priority", @@ -141,34 +163,52 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta label: property, value: property, })), + modal: false, }, ], [ { - label: "Blocker", - name: "blockers_list", - canSelectMultipleOptions: true, + label: "Parent", + name: "parent", + canSelectMultipleOptions: false, icon: UserIcon, - options: projectIssues?.results?.map((issue) => ({ - label: issue.name, - value: issue.id, - })), + issuesList: + issues?.results.filter( + (i) => + i.id !== issueDetail?.id && + i.id !== issueDetail?.parent && + i.parent !== issueDetail?.id + ) ?? [], + modal: true, + isOpen: isParentModalOpen, + setIsOpen: setIsParentModalOpen, }, + // { + // label: "Blocker", + // name: "blockers_list", + // canSelectMultipleOptions: true, + // icon: UserIcon, + // issuesList: issues?.results.filter((i) => i.id !== issueDetail?.id) ?? [], + // modal: true, + // isOpen: isBlockerModalOpen, + // setIsOpen: setIsBlockerModalOpen, + // }, + // { + // label: "Blocked", + // name: "blocked_list", + // canSelectMultipleOptions: true, + // icon: UserIcon, + // issuesList: issues?.results.filter((i) => i.id !== issueDetail?.id) ?? [], + // modal: true, + // isOpen: isBlockedModalOpen, + // setIsOpen: setIsBlockedModalOpen, + // }, { - label: "Blocked", - name: "blocked_list", - canSelectMultipleOptions: true, - icon: UserIcon, - options: projectIssues?.results?.map((issue) => ({ - label: issue.name, - value: issue.id, - })), - }, - { - label: "Due Date", + label: "Target Date", name: "target_date", canSelectMultipleOptions: true, icon: CalendarDaysIcon, + modal: false, }, ], [ @@ -181,6 +221,7 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta label: cycle.name, value: cycle.id, })), + modal: false, }, ], ]; @@ -249,12 +290,12 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta {sidebarSections.map((section, index) => (
    {section.map((item) => ( -
    -
    - +
    +
    +

    {item.label}

    -
    +
    {item.name === "target_date" ? ( = ({ control, submitChanges, issueDeta render={({ field: { value, onChange } }) => ( { submitChanges({ target_date: e.target.value }); onChange(e.target.value); }} - className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300" + className="hover:bg-gray-100 border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 w-full" /> )} /> + ) : item.modal ? ( + ( + <> + item.setIsOpen && item.setIsOpen(false)} + onChange={(val) => { + console.log(val); + // submitChanges({ [item.name]: val }); + onChange(val); + }} + issues={item?.issuesList ?? []} + title={`Select ${item.label}`} + multiple={item.canSelectMultipleOptions} + value={value} + /> + + + )} + /> ) : ( = ({ control, submitChanges, issueDeta > {({ open }) => (
    - + @@ -419,12 +497,12 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDeta + -
    -
    +
    +

    Label

    -
    +
    = ({ control, submitChanges, issueDeta <> Label
    - + {value && value.length > 0 diff --git a/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx b/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx index 26ea274a1..e911ae306 100644 --- a/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx +++ b/apps/app/components/project/issues/issue-detail/comment/IssueCommentCard.tsx @@ -64,14 +64,14 @@ const CommentCard: React.FC = ({ comment, onSubmit, handleCommentDeletion
    -
    +
    {comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? ( {comment.actor_detail.name} ) : (
    = ({ comment, onSubmit, handleCommentDeletion )}
    -

    - {comment.actor_detail.first_name} {comment.actor_detail.last_name} -

    -

    {timeAgo(comment.created_at)}

    -
    +
    {isEditing ? (