diff --git a/README.md b/README.md index a683ff341..67e66d8f4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Plane helps you track your issues, epics, and product roadmaps. Take off and exp

- + Discord diff --git a/components/project/issues/BoardView/SingleBoard.tsx b/components/project/issues/BoardView/SingleBoard.tsx index 1e79b3471..019b4ac5f 100644 --- a/components/project/issues/BoardView/SingleBoard.tsx +++ b/components/project/issues/BoardView/SingleBoard.tsx @@ -36,6 +36,7 @@ type Props = { >; bgColor?: string; stateId?: string; + createdBy?: string; }; const SingleBoard: React.FC = ({ @@ -48,6 +49,7 @@ const SingleBoard: React.FC = ({ setPreloadedData, bgColor = "#0f2b16", stateId, + createdBy, }) => { // Collapse/Expand const [show, setState] = useState(true); @@ -118,6 +120,8 @@ const SingleBoard: React.FC = ({ > {groupTitle === null || groupTitle === "null" ? "None" + : createdBy + ? createdBy : addSpaceIfCamelCase(groupTitle)} @@ -280,7 +284,7 @@ const SingleBoard: React.FC = ({ ) : (

{assignee.first_name.charAt(0)}
diff --git a/components/project/issues/BoardView/index.tsx b/components/project/issues/BoardView/index.tsx index 921a46972..2dfd5575c 100644 --- a/components/project/issues/BoardView/index.tsx +++ b/components/project/issues/BoardView/index.tsx @@ -18,9 +18,9 @@ import SingleBoard from "components/project/issues/BoardView/SingleBoard"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import CreateUpdateIssuesModal from "components/project/issues/CreateUpdateIssueModal"; // ui -import { Spinner, Button } from "ui"; +import { Spinner } from "ui"; // types -import type { IState, IIssue, Properties, NestedKeyOf } from "types"; +import type { IState, IIssue, Properties, NestedKeyOf, ProjectMember } from "types"; type Props = { properties: Properties; @@ -28,9 +28,10 @@ type Props = { groupedByIssues: { [key: string]: IIssue[]; }; + members: ProjectMember[] | undefined; }; -const BoardView: React.FC = ({ properties, selectedGroup, groupedByIssues }) => { +const BoardView: React.FC = ({ properties, selectedGroup, groupedByIssues, members }) => { const [isOpen, setIsOpen] = useState(false); const [isIssueOpen, setIsIssueOpen] = useState(false); @@ -164,7 +165,7 @@ const BoardView: React.FC = ({ properties, selectedGroup, groupedByIssues /> {groupedByIssues ? ( groupedByIssues ? ( -
+
@@ -180,6 +181,12 @@ const BoardView: React.FC = ({ properties, selectedGroup, groupedByIssues key={singleGroup} selectedGroup={selectedGroup} groupTitle={singleGroup} + createdBy={ + members + ? members?.find((m) => m.member.id === singleGroup)?.member + .first_name + : undefined + } groupedByIssues={groupedByIssues} index={index} setIsIssueOpen={setIsIssueOpen} diff --git a/components/project/issues/CreateUpdateIssueModal/SelectCycles.tsx b/components/project/issues/CreateUpdateIssueModal/SelectCycles.tsx index 7f15a1735..f66b0022d 100644 --- a/components/project/issues/CreateUpdateIssueModal/SelectCycles.tsx +++ b/components/project/issues/CreateUpdateIssueModal/SelectCycles.tsx @@ -46,7 +46,7 @@ const SelectSprint: React.FC = ({ control, setIsOpen }) => { leaveFrom="opacity-100" leaveTo="opacity-0" > - +
{sprints?.map((sprint) => ( = ({ control, setIsOpen }) => { {sprint.name} - - {selected && ( - - - )} )} diff --git a/components/project/issues/CreateUpdateIssueModal/SelectLabels.tsx b/components/project/issues/CreateUpdateIssueModal/SelectLabels.tsx index 5d7088c39..ede265c98 100644 --- a/components/project/issues/CreateUpdateIssueModal/SelectLabels.tsx +++ b/components/project/issues/CreateUpdateIssueModal/SelectLabels.tsx @@ -98,7 +98,7 @@ const SelectLabels: React.FC = ({ control }) => { leaveFrom="opacity-100" leaveTo="opacity-0" > - +
{issueLabels?.map((label) => ( = ({ control }) => { > {label.name} - - {selected ? ( - i === label.id) - ? "text-white" - : "text-indigo-600" - }`} - > - - ) : null} )} diff --git a/components/project/issues/CreateUpdateIssueModal/SelectPriority.tsx b/components/project/issues/CreateUpdateIssueModal/SelectPriority.tsx index 179379151..2452908cd 100644 --- a/components/project/issues/CreateUpdateIssueModal/SelectPriority.tsx +++ b/components/project/issues/CreateUpdateIssueModal/SelectPriority.tsx @@ -39,7 +39,7 @@ const SelectPriority: React.FC = ({ control }) => { leaveFrom="opacity-100" leaveTo="opacity-0" > - +
{PRIORITIES.map((priority) => ( = ({ control }) => { <> {priority} - - {selected ? ( - - - ) : null} )} diff --git a/components/project/issues/CreateUpdateIssueModal/SelectProject.tsx b/components/project/issues/CreateUpdateIssueModal/SelectProject.tsx index 2f188bbfb..762c655d3 100644 --- a/components/project/issues/CreateUpdateIssueModal/SelectProject.tsx +++ b/components/project/issues/CreateUpdateIssueModal/SelectProject.tsx @@ -50,7 +50,7 @@ const SelectProject: React.FC = ({ control }) => { leaveFrom="opacity-100" leaveTo="opacity-0" > - +
{projects ? ( projects.length > 0 ? ( diff --git a/components/project/issues/CreateUpdateIssueModal/index.tsx b/components/project/issues/CreateUpdateIssueModal/index.tsx index d6a717478..c993ea22d 100644 --- a/components/project/issues/CreateUpdateIssueModal/index.tsx +++ b/components/project/issues/CreateUpdateIssueModal/index.tsx @@ -147,7 +147,7 @@ const CreateUpdateIssuesModal: React.FC = ({ setToastAlert({ title: "Success", type: "success", - message: "Issue added to sprint successfully", + message: "Issue added to cycle successfully", }); }) .catch((err) => { @@ -394,7 +394,7 @@ const CreateUpdateIssuesModal: React.FC = ({ register={register} />
-
+
diff --git a/components/project/issues/issue-detail/IssueDetailSidebar.tsx b/components/project/issues/issue-detail/IssueDetailSidebar.tsx index c300d5ff0..985146cdc 100644 --- a/components/project/issues/issue-detail/IssueDetailSidebar.tsx +++ b/components/project/issues/issue-detail/IssueDetailSidebar.tsx @@ -19,11 +19,20 @@ import { PROJECT_ISSUE_LABELS, } from "constants/fetch-keys"; // commons -import { classNames } from "constants/common"; +import { classNames, copyTextToClipboard } from "constants/common"; // ui import { Input, Button } from "ui"; // icons -import { Bars3BottomRightIcon, PlusIcon, UserIcon, TagIcon } from "@heroicons/react/24/outline"; +import { + UserIcon, + TagIcon, + UserGroupIcon, + ChevronDownIcon, + Squares2X2Icon, + ChartBarIcon, + ClipboardDocumentIcon, + LinkIcon, +} from "@heroicons/react/24/outline"; // types import type { Control } from "react-hook-form"; import type { IIssue, IIssueLabels, IssueResponse, IState, WorkspaceMember } from "types"; @@ -31,6 +40,7 @@ import type { IIssue, IIssueLabels, IssueResponse, IState, WorkspaceMember } fro type Props = { control: Control; submitChanges: (formData: Partial) => void; + issueDetail: IIssue | undefined; }; const PRIORITIES = ["high", "medium", "low"]; @@ -39,7 +49,7 @@ const defaultValues: Partial = { name: "", }; -const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => { +const IssueDetailSidebar: React.FC = ({ control, submitChanges, issueDetail }) => { const { activeWorkspace, activeProject } = useUser(); const { data: states } = useSWR( @@ -90,65 +100,88 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => { }); }; + const sidebarOptions = [ + { + label: "Priority", + name: "priority", + canSelectMultipleOptions: false, + icon: ChartBarIcon, + options: PRIORITIES.map((property) => ({ + label: property, + value: property, + })), + }, + { + label: "Status", + name: "state", + canSelectMultipleOptions: false, + icon: Squares2X2Icon, + options: states?.map((state) => ({ + label: state.name, + value: state.id, + })), + }, + { + label: "Assignees", + name: "assignees_list", + canSelectMultipleOptions: true, + icon: UserGroupIcon, + options: people?.map((person) => ({ + label: person.member.first_name, + value: person.member.id, + })), + }, + { + label: "Blocker", + name: "blockers_list", + canSelectMultipleOptions: true, + icon: UserIcon, + options: projectIssues?.results?.map((issue) => ({ + label: issue.name, + value: issue.id, + })), + }, + { + label: "Blocked", + name: "blocked_list", + canSelectMultipleOptions: true, + icon: UserIcon, + options: projectIssues?.results?.map((issue) => ({ + label: issue.name, + value: issue.id, + })), + }, + ]; + return ( -
+
- {[ - { - label: "Priority", - name: "priority", - canSelectMultipleOptions: false, - icon: Bars3BottomRightIcon, - options: PRIORITIES.map((property) => ({ - label: property, - value: property, - })), - }, - { - label: "Status", - name: "state", - canSelectMultipleOptions: false, - icon: Bars3BottomRightIcon, - options: states?.map((state) => ({ - label: state.name, - value: state.id, - })), - }, - { - label: "Assignees", - name: "assignees_list", - canSelectMultipleOptions: true, - icon: UserIcon, - options: people?.map((person) => ({ - label: person.member.first_name, - value: person.member.id, - })), - }, - { - label: "Blocker", - name: "blockers_list", - canSelectMultipleOptions: true, - icon: UserIcon, - options: projectIssues?.results?.map((issue) => ({ - label: issue.name, - value: issue.id, - })), - }, - { - label: "Blocked", - name: "blocked_list", - canSelectMultipleOptions: true, - icon: UserIcon, - options: projectIssues?.results?.map((issue) => ({ - label: issue.name, - value: issue.id, - })), - }, - ].map((item) => ( -
-
- +

Quick Actions

+
+ + +
+ {sidebarOptions.map((item) => ( +
+
+

{item.label}

@@ -160,68 +193,61 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => { as="div" value={value} multiple={item.canSelectMultipleOptions} - onChange={(value) => submitChanges({ [item.name]: value })} + onChange={(value: any) => submitChanges({ [item.name]: value })} className="flex-shrink-0" > {({ open }) => ( - <> - {item.label} -
- - - - + + - + {value + ? Array.isArray(value) + ? value + .map( + (i: any) => + item.options?.find((option) => option.value === i)?.label + ) + .join(", ") || item.label + : item.options?.find((option) => option.value === value)?.label + : "None"} + + + + + + +
{item.options?.map((option) => ( - classNames( - active || selected ? "bg-indigo-50" : "bg-white", - "relative cursor-default select-none py-2 px-3" - ) + `${ + active || selected ? "text-white bg-theme" : "text-gray-900" + } ${ + item.label === "Priority" && "capitalize" + } cursor-pointer select-none relative p-2 rounded-md truncate` } value={option.value} > -
- - {option.label} - -
+ {option.label}
))} - - -
- +
+ + +
)} )} @@ -230,11 +256,11 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => {
))}
-
+ = ({ control, submitChanges }) => {
-
-
- +
+
+

Label

@@ -267,15 +293,11 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => { <> Label
- -
diff --git a/components/project/issues/issue-detail/activity/index.tsx b/components/project/issues/issue-detail/activity/index.tsx new file mode 100644 index 000000000..c48ba6ea8 --- /dev/null +++ b/components/project/issues/issue-detail/activity/index.tsx @@ -0,0 +1,122 @@ +// next +import Image from "next/image"; +import { + ChartBarIcon, + ChatBubbleBottomCenterTextIcon, + Squares2X2Icon, +} from "@heroicons/react/24/outline"; +import { addSpaceIfCamelCase, timeAgo } from "constants/common"; +import { IState } from "types"; +import { Spinner } from "ui"; + +type Props = { + issueActivities: any[] | undefined; + states: IState[] | undefined; +}; + +const activityIcons = { + state: , + priority: , + name: , + description: , +}; + +const IssueActivitySection: React.FC = ({ issueActivities, states }) => { + return ( + <> + {issueActivities ? ( +
+ {issueActivities.map((activity) => { + if (activity.field !== "updated_by") + return ( +
+ {issueActivities.length > 1 ? ( +
+ ); + })} +
+ ) : ( +
+ +
+ )} + + ); +}; + +export default IssueActivitySection; diff --git a/components/project/issues/comment/IssueCommentCard.tsx b/components/project/issues/issue-detail/comment/IssueCommentCard.tsx similarity index 100% rename from components/project/issues/comment/IssueCommentCard.tsx rename to components/project/issues/issue-detail/comment/IssueCommentCard.tsx diff --git a/components/project/issues/comment/IssueCommentSection.tsx b/components/project/issues/issue-detail/comment/IssueCommentSection.tsx similarity index 92% rename from components/project/issues/comment/IssueCommentSection.tsx rename to components/project/issues/issue-detail/comment/IssueCommentSection.tsx index ead953f2f..e0b043710 100644 --- a/components/project/issues/comment/IssueCommentSection.tsx +++ b/components/project/issues/issue-detail/comment/IssueCommentSection.tsx @@ -8,11 +8,14 @@ import issuesServices from "lib/services/issues.services"; // fetch keys import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys"; // components -import CommentCard from "components/project/issues/comment/IssueCommentCard"; +import CommentCard from "components/project/issues/issue-detail/comment/IssueCommentCard"; // ui import { TextArea, Button, Spinner } from "ui"; // types import type { IIssueComment } from "types"; +// icons +import UploadingIcon from "public/animated-icons/uploading.json"; + type Props = { comments?: IIssueComment[]; workspaceSlug: string; @@ -67,9 +70,9 @@ const IssueCommentSection: React.FC = ({ comments, issueId, projectId, wo }; return ( -
+
-
+