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
-
+
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
+
+
+ copyTextToClipboard(
+ `https://app.plane.so/projects/${activeProject?.id}/issues/${issueDetail?.id}`
+ )
+ }
+ >
+
+
+ copyTextToClipboard(`${issueDetail?.id}`)}
+ >
+
+
+
+ {sidebarOptions.map((item) => (
+
+
@@ -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(", ") || `Select ${item.label}`
- : item.options?.find((option) => option.value === value)?.label
- : `Select ${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 }) => {
))}
-
-
-
-
+
+
@@ -267,15 +293,11 @@ const IssueDetailSidebar: React.FC
= ({ control, submitChanges }) => {
<>
Label
-
-
+
{value && value.length > 0
@@ -285,8 +307,9 @@ const IssueDetailSidebar: React.FC = ({ control, submitChanges }) => {
issueLabels?.find((option) => option.id === i)?.name
)
.join(", ")
- : `Select label`}
+ : "None"}
+
= ({ control, submitChanges }) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
-
- {issueLabels?.map((label: any) => (
-
- classNames(
- active || selected ? "bg-indigo-50" : "bg-white",
- "relative cursor-default select-none py-2 px-3"
- )
- }
- value={label.id}
- >
-
-
- {label.name}
-
-
-
- ))}
+
+
+ {issueLabels?.map((label: any) => (
+
+ `${
+ active || selected ? "text-white bg-theme" : "text-gray-900"
+ } cursor-pointer select-none relative p-2 rounded-md truncate`
+ }
+ value={label.id}
+ >
+ {label.name}
+
+ ))}
+
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 ? (
+
+ ) : null}
+ {activity.field ? (
+
+
+ {activityIcons[activity.field as keyof typeof activityIcons]}
+
+
+ ) : (
+
+ {activity.actor_detail.avatar && activity.actor_detail.avatar !== "" ? (
+
+ ) : (
+
+ {activity.actor_detail.first_name.charAt(0)}
+
+ )}
+
+ )}
+
+
+
+ {activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
+ {activity.verb} {" "}
+ {activity.verb !== "created" ? (
+ {activity.field ?? "commented"}
+ ) : (
+ " this issue"
+ )}
+
+
{timeAgo(activity.created_at)}
+
+ {activity.verb !== "created" && (
+
+
+ From:{" "}
+
+ {activity.field === "state"
+ ? activity.old_value
+ ? addSpaceIfCamelCase(
+ states?.find((s) => s.id === activity.old_value)?.name ?? ""
+ )
+ : "None"
+ : activity.old_value}
+
+
+
+ To:{" "}
+
+ {activity.field === "state"
+ ? activity.new_value
+ ? addSpaceIfCamelCase(
+ states?.find((s) => s.id === activity.new_value)?.name ?? ""
+ )
+ : "None"
+ : activity.new_value}
+
+
+
+ )}
+
+
+
+ );
+ })}
+
+ ) : (
+
+
+
+ )}
+ >
+ );
+};
+
+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 (
-