mirror of
https://github.com/makeplane/plane.git
synced 2026-02-09 07:38:52 -06:00
[WEB-1680] dev: issue detail activity revamp and issue detail page improvement (#5075)
* chore: issue link activity message updated * chore: activity filter type constant added * dev: issue activity revamp and code refactor * chore: issue detail widget oreder updated in peek overview * chore: issue detail page padding improvement * fix: relation widget toast alert * fix: relation widget toast alert * fix: peek overview attachment delete modal * chore: code refactor * chore: code refactor * chore: code refactor * chore: code refactor * chore: issue detail sidebar parent field
This commit is contained in:
committed by
GitHub
parent
fd61079c8b
commit
53e5d4b40c
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trash } from "lucide-react";
|
||||
// ui
|
||||
@@ -33,9 +33,9 @@ export const IssueAttachmentsListItem: FC<TIssueAttachmentsListItem> = observer(
|
||||
const { getUserDetails } = useMember();
|
||||
const {
|
||||
attachment: { getAttachmentById },
|
||||
isDeleteAttachmentModalOpen,
|
||||
toggleDeleteAttachmentModal,
|
||||
} = useIssueDetail();
|
||||
// state
|
||||
const [isDeleteIssueAttachmentModalOpen, setIsDeleteIssueAttachmentModalOpen] = useState(false);
|
||||
|
||||
// derived values
|
||||
const attachment = attachmentId ? getAttachmentById(attachmentId) : undefined;
|
||||
@@ -46,10 +46,10 @@ export const IssueAttachmentsListItem: FC<TIssueAttachmentsListItem> = observer(
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDeleteIssueAttachmentModalOpen && (
|
||||
{isDeleteAttachmentModalOpen && (
|
||||
<IssueAttachmentDeleteModal
|
||||
isOpen={isDeleteIssueAttachmentModalOpen}
|
||||
onClose={() => setIsDeleteIssueAttachmentModalOpen(false)}
|
||||
isOpen={!!isDeleteAttachmentModalOpen}
|
||||
onClose={() => toggleDeleteAttachmentModal(null)}
|
||||
handleAttachmentOperations={handleAttachmentOperations}
|
||||
data={attachment}
|
||||
/>
|
||||
@@ -95,7 +95,7 @@ export const IssueAttachmentsListItem: FC<TIssueAttachmentsListItem> = observer(
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDeleteIssueAttachmentModalOpen(true);
|
||||
toggleDeleteAttachmentModal(attachmentId);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -160,7 +160,6 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||
onSubmit={async () =>
|
||||
await issueOperations.remove(workspaceSlug, projectId, issueCrudState?.delete?.issue?.id as string)
|
||||
}
|
||||
isSubIssue
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -71,22 +71,12 @@ export const useRelationOperations = (): TRelationIssueOperations => {
|
||||
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
await removeIssue(workspaceSlug, projectId, issueId);
|
||||
setToast({
|
||||
title: "Success!",
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
message: "Issue deleted successfully",
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_DELETED,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
setToast({
|
||||
title: "Error!",
|
||||
type: TOAST_TYPE.ERROR,
|
||||
message: "Issue delete failed",
|
||||
});
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_DELETED,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { EActivityFilterType } from "@/constants/issue";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// components
|
||||
import { IssueActivityList } from "./activity/activity-list";
|
||||
import { IssueActivityItem } from "./activity/activity-list";
|
||||
import { IssueCommentCard } from "./comments/comment-card";
|
||||
// types
|
||||
import { TActivityOperations } from "./root";
|
||||
@@ -12,13 +13,15 @@ type TIssueActivityCommentRoot = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
selectedFilters: EActivityFilterType[];
|
||||
activityOperations: TActivityOperations;
|
||||
showAccessSpecifier?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer((props) => {
|
||||
const { workspaceSlug, issueId, activityOperations, showAccessSpecifier, projectId, disabled } = props;
|
||||
const { workspaceSlug, issueId, selectedFilters, activityOperations, showAccessSpecifier, projectId, disabled } =
|
||||
props;
|
||||
// hooks
|
||||
const {
|
||||
activity: { getActivityCommentByIssueId },
|
||||
@@ -28,9 +31,19 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
const activityComments = getActivityCommentByIssueId(issueId);
|
||||
|
||||
if (!activityComments || (activityComments && activityComments.length <= 0)) return <></>;
|
||||
|
||||
const isCommentFilterSelected = selectedFilters.includes(EActivityFilterType.COMMENT);
|
||||
const isActivityFilterSelected = selectedFilters.includes(EActivityFilterType.ACTIVITY);
|
||||
|
||||
const filteredActivityComments = activityComments.filter(
|
||||
(activityComment) =>
|
||||
(activityComment.activity_type === "COMMENT" && isCommentFilterSelected) ||
|
||||
(activityComment.activity_type === "ACTIVITY" && isActivityFilterSelected)
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{activityComments.map((activityComment, index) =>
|
||||
{filteredActivityComments.map((activityComment, index) =>
|
||||
activityComment.activity_type === "COMMENT" ? (
|
||||
<IssueCommentCard
|
||||
projectId={projectId}
|
||||
@@ -38,14 +51,14 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
workspaceSlug={workspaceSlug}
|
||||
commentId={activityComment.id}
|
||||
activityOperations={activityOperations}
|
||||
ends={index === 0 ? "top" : index === activityComments.length - 1 ? "bottom" : undefined}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
showAccessSpecifier={showAccessSpecifier}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : activityComment.activity_type === "ACTIVITY" ? (
|
||||
<IssueActivityList
|
||||
<IssueActivityItem
|
||||
activityId={activityComment.id}
|
||||
ends={index === 0 ? "top" : index === activityComments.length - 1 ? "bottom" : undefined}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { FC, Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Check, ListFilter } from "lucide-react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// constants
|
||||
import { ACTIVITY_FILTER_TYPE_OPTIONS, EActivityFilterType } from "@/constants/issue";
|
||||
// helper
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
type Props = {
|
||||
selectedFilters: EActivityFilterType[];
|
||||
toggleFilter: (filter: EActivityFilterType) => void;
|
||||
};
|
||||
|
||||
export const ActivityFilter: FC<Props> = observer((props) => {
|
||||
const { selectedFilters, toggleFilter } = props;
|
||||
return (
|
||||
<Popover as="div" className="relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
size="sm"
|
||||
prependIcon={<ListFilter className="h-3 w-3" />}
|
||||
className="relative"
|
||||
>
|
||||
<span className={`${open ? "text-custom-text-100" : "text-custom-text-200"}`}>Filters</span>
|
||||
</Button>
|
||||
</Popover.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel className="absolute mt-2 right-0 z-10 min-w-40">
|
||||
<div className="p-2 rounded-md border border-custom-border-200 bg-custom-background-100">
|
||||
{ACTIVITY_FILTER_TYPE_OPTIONS.map((filter) => {
|
||||
const isSelected = selectedFilters.includes(filter.value);
|
||||
return (
|
||||
<div
|
||||
key={filter.value}
|
||||
className="flex items-center gap-2 text-sm cursor-pointer px-2 p-1 transition-all hover:bg-custom-background-80 rounded-sm"
|
||||
onClick={() => toggleFilter(filter.value)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 w-3 h-3 flex justify-center items-center rounded-sm transition-all bg-custom-background-90",
|
||||
{
|
||||
"bg-custom-primary text-white": isSelected,
|
||||
"bg-custom-background-80 text-custom-text-400": isSelected && selectedFilters.length === 1,
|
||||
"bg-custom-background-90": !isSelected,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{isSelected && <Check className="h-2.5 w-2.5" />}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap",
|
||||
isSelected ? "text-custom-text-100" : "text-custom-text-200"
|
||||
)}
|
||||
>
|
||||
{filter.label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export const IssueLinkActivity: FC<TIssueLinkActivity> = observer((props) => {
|
||||
<>
|
||||
{activity.verb === "created" ? (
|
||||
<>
|
||||
<span>added this </span>
|
||||
<span>added </span>
|
||||
<a
|
||||
href={`${activity.new_value}`}
|
||||
target="_blank"
|
||||
|
||||
@@ -24,12 +24,12 @@ import {
|
||||
IssueInboxActivity,
|
||||
} from "./actions";
|
||||
|
||||
type TIssueActivityList = {
|
||||
type TIssueActivityItem = {
|
||||
activityId: string;
|
||||
ends: "top" | "bottom" | undefined;
|
||||
};
|
||||
|
||||
export const IssueActivityList: FC<TIssueActivityList> = observer((props) => {
|
||||
export const IssueActivityItem: FC<TIssueActivityItem> = observer((props) => {
|
||||
const { activityId, ends } = props;
|
||||
// hooks
|
||||
const {
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from "./activity-comment-root";
|
||||
|
||||
// activity
|
||||
export * from "./activity/activity-list";
|
||||
export * from "./activity-filter";
|
||||
|
||||
// issue comment
|
||||
export * from "./comments";
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import { FC, Fragment, useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { History, LucideIcon, MessageCircle } from "lucide-react";
|
||||
// types
|
||||
import { TIssueComment } from "@plane/types";
|
||||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { IssueActivityCommentRoot, IssueCommentRoot, IssueCommentCreate } from "@/components/issues";
|
||||
import { ActivityFilter, IssueActivityCommentRoot, IssueCommentCreate } from "@/components/issues";
|
||||
// constants
|
||||
import { EActivityFilterType } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject } from "@/hooks/store";
|
||||
|
||||
@@ -19,21 +20,6 @@ type TIssueActivity = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type TActivityTabs = "all" | "comments";
|
||||
|
||||
const activityTabs: { key: TActivityTabs; title: string; icon: LucideIcon }[] = [
|
||||
{
|
||||
key: "comments",
|
||||
title: "Comments",
|
||||
icon: MessageCircle,
|
||||
},
|
||||
{
|
||||
key: "all",
|
||||
title: "All activity",
|
||||
icon: History,
|
||||
},
|
||||
];
|
||||
|
||||
export type TActivityOperations = {
|
||||
createComment: (data: Partial<TIssueComment>) => Promise<void>;
|
||||
updateComment: (commentId: string, data: Partial<TIssueComment>) => Promise<void>;
|
||||
@@ -46,7 +32,18 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
||||
const { createComment, updateComment, removeComment } = useIssueDetail();
|
||||
const { getProjectById } = useProject();
|
||||
// state
|
||||
const [activityTab, setActivityTab] = useState<TActivityTabs>("comments");
|
||||
const [selectedFilters, setSelectedFilters] = useState([EActivityFilterType.COMMENT, EActivityFilterType.ACTIVITY]);
|
||||
// toggle filter
|
||||
const toggleFilter = (filter: EActivityFilterType) => {
|
||||
setSelectedFilters((prevFilters) => {
|
||||
if (prevFilters.includes(filter)) {
|
||||
if (prevFilters.length === 1) return prevFilters; // Ensure at least one filter is applied
|
||||
return prevFilters.filter((f) => f !== filter);
|
||||
} else {
|
||||
return [...prevFilters, filter];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const activityOperations: TActivityOperations = useMemo(
|
||||
() => ({
|
||||
@@ -109,74 +106,36 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
||||
if (!project) return <></>;
|
||||
|
||||
return (
|
||||
<div className="space-y-3 pt-3">
|
||||
<div className="space-y-4 pt-3">
|
||||
{/* header */}
|
||||
<div className="text-lg text-custom-text-100">Activity</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-lg text-custom-text-100">Activity</div>
|
||||
<ActivityFilter selectedFilters={selectedFilters} toggleFilter={toggleFilter} />
|
||||
</div>
|
||||
|
||||
{/* rendering activity */}
|
||||
<div className="space-y-3">
|
||||
<div className="relative flex items-center gap-1">
|
||||
{activityTabs.map((tab) => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`relative flex items-center px-2 py-1.5 gap-1 cursor-pointer transition-all rounded
|
||||
${
|
||||
tab.key === activityTab
|
||||
? `text-custom-text-100 bg-custom-background-80`
|
||||
: `text-custom-text-200 hover:bg-custom-background-80`
|
||||
}`}
|
||||
onClick={() => setActivityTab(tab.key)}
|
||||
>
|
||||
<div className="flex-shrink-0 w-4 h-4 flex justify-center items-center">
|
||||
<tab.icon className="w-3 h-3" />
|
||||
</div>
|
||||
<div className="text-sm">{tab.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="min-h-[200px]">
|
||||
{activityTab === "all" ? (
|
||||
<div className="space-y-3">
|
||||
<IssueActivityCommentRoot
|
||||
<div className="space-y-3">
|
||||
<IssueActivityCommentRoot
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
issueId={issueId}
|
||||
selectedFilters={selectedFilters}
|
||||
activityOperations={activityOperations}
|
||||
showAccessSpecifier={!!project.anchor}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && (
|
||||
<IssueCommentCreate
|
||||
issueId={issueId}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
issueId={issueId}
|
||||
activityOperations={activityOperations}
|
||||
showAccessSpecifier={!!project.anchor}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && (
|
||||
<IssueCommentCreate
|
||||
issueId={issueId}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
activityOperations={activityOperations}
|
||||
showAccessSpecifier={!!project.anchor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<IssueCommentRoot
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
issueId={issueId}
|
||||
activityOperations={activityOperations}
|
||||
showAccessSpecifier={!!project.anchor}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && (
|
||||
<IssueCommentCreate
|
||||
issueId={issueId}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
activityOperations={activityOperations}
|
||||
showAccessSpecifier={!!project.anchor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-lg space-y-4 pl-3">
|
||||
<div className="rounded-lg space-y-4">
|
||||
{issue.parent_id && (
|
||||
<IssueParentDetail
|
||||
workspaceSlug={workspaceSlug}
|
||||
@@ -117,18 +117,14 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="pl-3">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</div>
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
|
||||
<div className="pl-3">
|
||||
<IssueActivity workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={isArchived} />
|
||||
</div>
|
||||
<IssueActivity workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={isArchived} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -348,7 +348,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full overflow-hidden">
|
||||
<div className="max-w-2/3 h-full w-full space-y-8 overflow-y-auto px-6 py-5">
|
||||
<div className="max-w-2/3 h-full w-full space-y-8 overflow-y-auto px-9 py-5">
|
||||
<IssueMainContent
|
||||
workspaceSlug={workspaceSlug}
|
||||
swrIssueDetails={swrIssueDetails}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { CalendarCheck2, CalendarClock, Signal, Tag, Triangle, UserCircle2, Users } from "lucide-react";
|
||||
import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users } from "lucide-react";
|
||||
// ui
|
||||
import { ContrastIcon, DiceIcon, DoubleCircleIcon } from "@plane/ui";
|
||||
// components
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
StateDropdown,
|
||||
} from "@/components/dropdowns";
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { IssueCycleSelect, IssueLabel, IssueModuleSelect } from "@/components/issues";
|
||||
import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect } from "@/components/issues";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
@@ -250,6 +250,21 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex h-8 items-center gap-2">
|
||||
<div className="flex w-2/5 flex-shrink-0 items-center gap-1 text-sm text-custom-text-300">
|
||||
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Parent</span>
|
||||
</div>
|
||||
<IssueParentSelect
|
||||
className="h-full w-3/5 flex-grow"
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issueOperations={issueOperations}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-8 gap-2">
|
||||
<div className="flex w-2/5 flex-shrink-0 gap-1 pt-2 text-sm text-custom-text-300">
|
||||
<Tag className="h-4 w-4 flex-shrink-0" />
|
||||
|
||||
@@ -184,13 +184,6 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<PeekOverviewProperties
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@@ -199,6 +192,15 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
disabled={disabled || is_archived}
|
||||
/>
|
||||
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
@@ -221,12 +223,14 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||
/>
|
||||
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className="py-2">
|
||||
<IssueDetailWidgets
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IssueActivity
|
||||
workspaceSlug={workspaceSlug}
|
||||
|
||||
@@ -485,3 +485,19 @@ export enum EServerGroupByToFilterOptions {
|
||||
"project_id" = "project",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EActivityFilterType {
|
||||
COMMENT = "COMMENT",
|
||||
ACTIVITY = "ACTIVITY",
|
||||
}
|
||||
|
||||
export const ACTIVITY_FILTER_TYPE_OPTIONS = [
|
||||
{
|
||||
value: EActivityFilterType.COMMENT,
|
||||
label: "Comments",
|
||||
},
|
||||
{
|
||||
value: EActivityFilterType.ACTIVITY,
|
||||
label: "Updates",
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user