mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge pull request #1750 from makeplane/develop
promote: develop to stage-release
This commit is contained in:
commit
9f69fe6060
@ -291,7 +291,8 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class IssueActivitySerializer(BaseSerializer):
|
class IssueActivitySerializer(BaseSerializer):
|
||||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
|
||||||
|
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueActivity
|
model = IssueActivity
|
||||||
|
@ -477,7 +477,7 @@ class IssueActivityEndpoint(BaseAPIView):
|
|||||||
~Q(field="comment"),
|
~Q(field="comment"),
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
)
|
)
|
||||||
.select_related("actor", "workspace")
|
.select_related("actor", "workspace", "issue", "project")
|
||||||
).order_by("created_at")
|
).order_by("created_at")
|
||||||
issue_comments = (
|
issue_comments = (
|
||||||
IssueComment.objects.filter(issue_id=issue_id)
|
IssueComment.objects.filter(issue_id=issue_id)
|
||||||
|
@ -140,7 +140,7 @@ class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
try:
|
try:
|
||||||
queryset = IssueActivity.objects.filter(actor=request.user).select_related(
|
queryset = IssueActivity.objects.filter(actor=request.user).select_related(
|
||||||
"actor", "workspace"
|
"actor", "workspace", "issue", "project"
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.paginate(
|
return self.paginate(
|
||||||
|
@ -267,7 +267,7 @@ class ProjectViewSet(BaseViewSet):
|
|||||||
status=status.HTTP_410_GONE,
|
status=status.HTTP_410_GONE,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pr(e)
|
capture_exception(e)
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Something went wrong please try again later"},
|
{"error": "Something went wrong please try again later"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
@ -1190,7 +1190,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
actor=user_id,
|
actor=user_id,
|
||||||
).select_related("actor", "workspace")
|
).select_related("actor", "workspace", "issue", "project")
|
||||||
|
|
||||||
if projects:
|
if projects:
|
||||||
queryset = queryset.filter(project__in=projects)
|
queryset = queryset.filter(project__in=projects)
|
||||||
|
@ -68,4 +68,14 @@ class Migration(migrations.Migration):
|
|||||||
'unique_together': {('comment', 'actor', 'reaction')},
|
'unique_together': {('comment', 'actor', 'reaction')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='identifier',
|
||||||
|
field=models.CharField(max_length=12, verbose_name='Project Identifier'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='projectidentifier',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=12),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { Icon } from "components/ui";
|
import { Icon, Tooltip } from "components/ui";
|
||||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||||
import { BlockedIcon, BlockerIcon } from "components/icons";
|
import { BlockedIcon, BlockerIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
@ -8,26 +10,65 @@ import { capitalizeFirstLetter } from "helpers/string.helper";
|
|||||||
// types
|
// types
|
||||||
import { IIssueActivity } from "types";
|
import { IIssueActivity } from "types";
|
||||||
|
|
||||||
export const activityDetails: {
|
const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={
|
||||||
|
activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||||
|
>
|
||||||
|
{activity.issue_detail
|
||||||
|
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
|
||||||
|
: "Issue"}
|
||||||
|
<Icon iconName="launch" className="!text-xs" />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const activityDetails: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
message: (activity: IIssueActivity) => React.ReactNode;
|
message: (activity: IIssueActivity, showIssue: boolean) => React.ReactNode;
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
assignees: {
|
assignees: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (activity.old_value === "")
|
if (activity.old_value === "")
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
added a new assignee{" "}
|
added a new assignee{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
to <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
removed the assignee{" "}
|
removed the assignee{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -41,7 +82,7 @@ export const activityDetails: {
|
|||||||
icon: <Icon iconName="archive" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="archive" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (activity.verb === "created")
|
if (activity.verb === "created")
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -55,9 +96,27 @@ export const activityDetails: {
|
|||||||
attachment
|
attachment
|
||||||
<Icon iconName="launch" className="!text-xs" />
|
<Icon iconName="launch" className="!text-xs" />
|
||||||
</a>
|
</a>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
to <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
removed an attachment
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
else return "removed an attachment.";
|
|
||||||
},
|
},
|
||||||
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
@ -126,17 +185,47 @@ export const activityDetails: {
|
|||||||
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
message: (activity) => "updated the description.",
|
message: (activity, showIssue) => (
|
||||||
|
<>
|
||||||
|
updated the description
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
of <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
),
|
||||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
estimate_point: {
|
estimate_point: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (!activity.new_value) return "removed the estimate point.";
|
if (!activity.new_value)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
removed the estimate point
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
set the estimate point to{" "}
|
set the estimate point to{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
for <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -150,7 +239,7 @@ export const activityDetails: {
|
|||||||
icon: <Icon iconName="stack" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="stack" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (activity.old_value === "")
|
if (activity.old_value === "")
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -165,6 +254,12 @@ export const activityDetails: {
|
|||||||
/>
|
/>
|
||||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||||
</span>
|
</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
to <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
@ -181,13 +276,19 @@ export const activityDetails: {
|
|||||||
/>
|
/>
|
||||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||||
</span>
|
</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (activity.verb === "created")
|
if (activity.verb === "created")
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -200,8 +301,14 @@ export const activityDetails: {
|
|||||||
>
|
>
|
||||||
link
|
link
|
||||||
<Icon iconName="launch" className="!text-xs" />
|
<Icon iconName="launch" className="!text-xs" />
|
||||||
</a>{" "}
|
</a>
|
||||||
to the issue.
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
to <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
@ -216,8 +323,14 @@ export const activityDetails: {
|
|||||||
>
|
>
|
||||||
link
|
link
|
||||||
<Icon iconName="launch" className="!text-xs" />
|
<Icon iconName="launch" className="!text-xs" />
|
||||||
</a>{" "}
|
</a>
|
||||||
from the issue.
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -250,52 +363,102 @@ export const activityDetails: {
|
|||||||
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
message: (activity) => `set the name to ${activity.new_value}.`,
|
message: (activity, showIssue) => (
|
||||||
|
<>
|
||||||
|
set the name to {activity.new_value}
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
of <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
),
|
||||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
parent: {
|
parent: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (!activity.new_value)
|
if (!activity.new_value)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
removed the parent{" "}
|
removed the parent{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
set the parent to{" "}
|
set the parent to{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
for <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
priority: {
|
priority: {
|
||||||
message: (activity) => (
|
message: (activity, showIssue) => (
|
||||||
<>
|
<>
|
||||||
set the priority to{" "}
|
set the priority to{" "}
|
||||||
<span className="font-medium text-custom-text-100">
|
<span className="font-medium text-custom-text-100">
|
||||||
{activity.new_value ? capitalizeFirstLetter(activity.new_value) : "None"}
|
{activity.new_value ? capitalizeFirstLetter(activity.new_value) : "None"}
|
||||||
</span>
|
</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
for <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
.
|
.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
message: (activity) => (
|
message: (activity, showIssue) => (
|
||||||
<>
|
<>
|
||||||
set the state to{" "}
|
set the state to{" "}
|
||||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
for <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
icon: <Squares2X2Icon className="h-3 w-3" aria-hidden="true" />,
|
icon: <Squares2X2Icon className="h-3 w-3" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
target_date: {
|
target_date: {
|
||||||
message: (activity) => {
|
message: (activity, showIssue) => {
|
||||||
if (!activity.new_value) return "removed the due date.";
|
if (!activity.new_value)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
removed the due date
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
from <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -303,6 +466,12 @@ export const activityDetails: {
|
|||||||
<span className="font-medium text-custom-text-100">
|
<span className="font-medium text-custom-text-100">
|
||||||
{renderShortDateWithYearFormat(activity.new_value)}
|
{renderShortDateWithYearFormat(activity.new_value)}
|
||||||
</span>
|
</span>
|
||||||
|
{showIssue && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
for <IssueLink activity={activity} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
.
|
.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -310,3 +479,19 @@ export const activityDetails: {
|
|||||||
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
|
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ActivityIcon = ({ activity }: { activity: IIssueActivity }) => (
|
||||||
|
<>{activityDetails[activity.field as keyof typeof activityDetails]?.icon}</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ActivityMessage = ({
|
||||||
|
activity,
|
||||||
|
showIssue = false,
|
||||||
|
}: {
|
||||||
|
activity: IIssueActivity;
|
||||||
|
showIssue?: boolean;
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
{activityDetails[activity.field as keyof typeof activityDetails]?.message(activity, showIssue)}
|
||||||
|
</>
|
||||||
|
);
|
@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
@ -99,7 +101,11 @@ const activityDetails: {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Feeds: React.FC<any> = ({ activities }) => (
|
export const Feeds: React.FC<any> = ({ activities }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ul role="list" className="-mb-4">
|
<ul role="list" className="-mb-4">
|
||||||
{activities.map((activity: any, activityIdx: number) => {
|
{activities.map((activity: any, activityIdx: number) => {
|
||||||
@ -152,11 +158,11 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
activity.field !== "link" &&
|
activity.field !== "link" &&
|
||||||
activity.field !== "estimate"
|
activity.field !== "estimate"
|
||||||
) {
|
) {
|
||||||
const { workspace_detail, project, issue } = activity;
|
const { project, issue } = activity;
|
||||||
value = (
|
value = (
|
||||||
<span className="text-custom-text-200">
|
<span className="text-custom-text-200">
|
||||||
created{" "}
|
created{" "}
|
||||||
<Link href={`/${workspace_detail.slug}/projects/${project}/issues/${issue}`}>
|
<Link href={`/${workspaceSlug}/projects/${project}/issues/${issue}`}>
|
||||||
<a className="inline-flex items-center hover:underline">
|
<a className="inline-flex items-center hover:underline">
|
||||||
this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" />
|
this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" />
|
||||||
</a>
|
</a>
|
||||||
@ -193,7 +199,8 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
value = "link";
|
value = "link";
|
||||||
} else if (activity.field === "estimate_point") {
|
} else if (activity.field === "estimate_point") {
|
||||||
value = activity.new_value
|
value = activity.new_value
|
||||||
? activity.new_value + ` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}`
|
? activity.new_value +
|
||||||
|
` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}`
|
||||||
: "None";
|
: "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +242,9 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
{activity.actor_detail.first_name}
|
{activity.actor_detail.first_name}
|
||||||
{activity.actor_detail.is_bot ? "Bot" : " " + activity.actor_detail.last_name}
|
{activity.actor_detail.is_bot
|
||||||
|
? "Bot"
|
||||||
|
: " " + activity.actor_detail.last_name}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 text-xs text-custom-text-200">
|
<p className="mt-0.5 text-xs text-custom-text-200">
|
||||||
Commented {timeAgo(activity.created_at)}
|
Commented {timeAgo(activity.created_at)}
|
||||||
@ -276,7 +285,8 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
<div className="mt-1.5">
|
<div className="mt-1.5">
|
||||||
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
||||||
{activity.field ? (
|
{activity.field ? (
|
||||||
activityDetails[activity.field as keyof typeof activityDetails]?.icon
|
activityDetails[activity.field as keyof typeof activityDetails]
|
||||||
|
?.icon
|
||||||
) : activity.actor_detail.avatar &&
|
) : activity.actor_detail.avatar &&
|
||||||
activity.actor_detail.avatar !== "" ? (
|
activity.actor_detail.avatar !== "" ? (
|
||||||
<img
|
<img
|
||||||
@ -328,4 +338,5 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -3,6 +3,7 @@ export * from "./modals";
|
|||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
export * from "./theme";
|
export * from "./theme";
|
||||||
export * from "./views";
|
export * from "./views";
|
||||||
|
export * from "./activity";
|
||||||
export * from "./feeds";
|
export * from "./feeds";
|
||||||
export * from "./reaction-selector";
|
export * from "./reaction-selector";
|
||||||
export * from "./image-picker-popover";
|
export * from "./image-picker-popover";
|
||||||
|
@ -8,12 +8,12 @@ import useSWR from "swr";
|
|||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
// components
|
// components
|
||||||
|
import { ActivityIcon, ActivityMessage } from "components/core";
|
||||||
import { CommentCard } from "components/issues/comment";
|
import { CommentCard } from "components/issues/comment";
|
||||||
// ui
|
// ui
|
||||||
import { Icon, Loader } from "components/ui";
|
import { Icon, Loader } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { timeAgo } from "helpers/date-time.helper";
|
import { timeAgo } from "helpers/date-time.helper";
|
||||||
import { activityDetails } from "helpers/activity.helper";
|
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssueComment } from "types";
|
import { ICurrentUserResponse, IIssueComment } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -91,11 +91,11 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
|||||||
<ul role="list" className="-mb-4">
|
<ul role="list" className="-mb-4">
|
||||||
{issueActivities.map((activityItem, index) => {
|
{issueActivities.map((activityItem, index) => {
|
||||||
// determines what type of action is performed
|
// determines what type of action is performed
|
||||||
const message = activityItem.field
|
const message = activityItem.field ? (
|
||||||
? activityDetails[activityItem.field as keyof typeof activityDetails]?.message(
|
<ActivityMessage activity={activityItem} />
|
||||||
activityItem
|
) : (
|
||||||
)
|
"created the issue."
|
||||||
: "created the issue.";
|
);
|
||||||
|
|
||||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||||
return (
|
return (
|
||||||
@ -116,8 +116,7 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
|||||||
activityItem.new_value === "restore" ? (
|
activityItem.new_value === "restore" ? (
|
||||||
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
||||||
) : (
|
) : (
|
||||||
activityDetails[activityItem.field as keyof typeof activityDetails]
|
<ActivityIcon activity={activityItem} />
|
||||||
?.icon
|
|
||||||
)
|
)
|
||||||
) : activityItem.actor_detail.avatar &&
|
) : activityItem.actor_detail.avatar &&
|
||||||
activityItem.actor_detail.avatar !== "" ? (
|
activityItem.actor_detail.avatar !== "" ? (
|
||||||
|
@ -140,11 +140,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
|
|||||||
ref={showEditorRef}
|
ref={showEditorRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CommentReaction
|
<CommentReaction projectId={comment.project} commentId={comment.id} />
|
||||||
workspaceSlug={comment?.workspace_detail?.slug}
|
|
||||||
projectId={comment.project}
|
|
||||||
commentId={comment.id}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useCommentReaction from "hooks/use-comment-reaction";
|
import useCommentReaction from "hooks/use-comment-reaction";
|
||||||
@ -9,13 +11,15 @@ import { ReactionSelector } from "components/core";
|
|||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug?: string | string[];
|
|
||||||
projectId?: string | string[];
|
projectId?: string | string[];
|
||||||
commentId: string;
|
commentId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentReaction: React.FC<Props> = (props) => {
|
export const CommentReaction: React.FC<Props> = (props) => {
|
||||||
const { workspaceSlug, projectId, commentId } = props;
|
const { projectId, commentId } = props;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import useToast from "hooks/use-toast";
|
|||||||
import useInboxView from "hooks/use-inbox-view";
|
import useInboxView from "hooks/use-inbox-view";
|
||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
|
import useMyIssues from "hooks/my-issues/use-my-issues";
|
||||||
// components
|
// components
|
||||||
import { IssueForm } from "components/issues";
|
import { IssueForm } from "components/issues";
|
||||||
// types
|
// types
|
||||||
@ -85,6 +86,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
|
|
||||||
|
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
||||||
@ -243,6 +246,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
if (issueView === "calendar") mutate(calendarFetchKey);
|
if (issueView === "calendar") mutate(calendarFetchKey);
|
||||||
if (issueView === "gantt_chart") mutate(ganttFetchKey);
|
if (issueView === "gantt_chart") mutate(ganttFetchKey);
|
||||||
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey);
|
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey);
|
||||||
|
if (groupedIssues) mutateMyIssues();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
|
@ -146,13 +146,16 @@ export const MyIssuesViewOptions: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Group by</h4>
|
<h4 className="text-custom-text-200">Group by</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
groupBy === "project"
|
groupBy === "project"
|
||||||
? "Project"
|
? "Project"
|
||||||
: GROUP_BY_OPTIONS.find((option) => option.key === groupBy)?.name ??
|
: GROUP_BY_OPTIONS.find((option) => option.key === groupBy)
|
||||||
"Select"
|
?.name ?? "Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{GROUP_BY_OPTIONS.map((option) => {
|
{GROUP_BY_OPTIONS.map((option) => {
|
||||||
if (issueView === "kanban" && option.key === null) return null;
|
if (issueView === "kanban" && option.key === null) return null;
|
||||||
@ -170,16 +173,21 @@ export const MyIssuesViewOptions: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Order by</h4>
|
<h4 className="text-custom-text-200">Order by</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
||||||
"Select"
|
"Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{ORDER_BY_OPTIONS.map((option) => {
|
{ORDER_BY_OPTIONS.map((option) => {
|
||||||
if (groupBy === "priority" && option.key === "priority") return null;
|
if (groupBy === "priority" && option.key === "priority")
|
||||||
|
return null;
|
||||||
if (option.key === "sort_order") return null;
|
if (option.key === "sort_order") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -195,15 +203,19 @@ export const MyIssuesViewOptions: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Issue type</h4>
|
<h4 className="text-custom-text-200">Issue type</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type)
|
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type)
|
||||||
?.name ?? "Select"
|
?.name ?? "Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{FILTER_ISSUE_OPTIONS.map((option) => (
|
{FILTER_ISSUE_OPTIONS.map((option) => (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
@ -219,32 +231,23 @@ export const MyIssuesViewOptions: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Show empty states</h4>
|
<h4 className="text-custom-text-200">Show empty states</h4>
|
||||||
|
<div className="w-28">
|
||||||
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} />
|
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} />
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="relative flex justify-end gap-x-3">
|
</div>
|
||||||
<button type="button" onClick={() => resetFilterToDefault()}>
|
|
||||||
Reset to default
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="font-medium text-custom-primary"
|
|
||||||
onClick={() => setNewFilterDefaultView()}
|
|
||||||
>
|
|
||||||
Set as default
|
|
||||||
</button>
|
|
||||||
</div> */}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2 text-custom-text-200">
|
||||||
{Object.keys(properties).map((key) => {
|
{Object.keys(properties).map((key) => {
|
||||||
if (key === "estimate" && !isEstimateActive) return null;
|
if (key === "estimate" && !isEstimateActive) return null;
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
|
// components
|
||||||
|
import { ActivityMessage } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { Icon, Loader } from "components/ui";
|
import { Icon, Loader } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { activityDetails } from "helpers/activity.helper";
|
|
||||||
import { timeAgo } from "helpers/date-time.helper";
|
import { timeAgo } from "helpers/date-time.helper";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
||||||
@ -55,12 +55,12 @@ export const ProfileActivity = () => {
|
|||||||
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
||||||
</span>
|
</span>
|
||||||
{activity.field ? (
|
{activity.field ? (
|
||||||
activityDetails[activity.field]?.message(activity as any)
|
<ActivityMessage activity={activity} showIssue />
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
created this{" "}
|
created this{" "}
|
||||||
<a
|
<a
|
||||||
href={`/${activity.workspace_detail.slug}/projects/${activity.project}/issues/${activity.issue}`}
|
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||||
|
@ -172,13 +172,17 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Group by</h4>
|
<h4 className="text-custom-text-200">Group by</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
groupByProperty === "project"
|
groupByProperty === "project"
|
||||||
? "Project"
|
? "Project"
|
||||||
: GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty)
|
: GROUP_BY_OPTIONS.find(
|
||||||
?.name ?? "Select"
|
(option) => option.key === groupByProperty
|
||||||
|
)?.name ?? "Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{GROUP_BY_OPTIONS.map((option) => {
|
{GROUP_BY_OPTIONS.map((option) => {
|
||||||
if (issueView === "kanban" && option.key === null) return null;
|
if (issueView === "kanban" && option.key === null) return null;
|
||||||
@ -196,13 +200,17 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Order by</h4>
|
<h4 className="text-custom-text-200">Order by</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ??
|
||||||
"Select"
|
"Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{ORDER_BY_OPTIONS.map((option) => {
|
{ORDER_BY_OPTIONS.map((option) => {
|
||||||
if (groupByProperty === "priority" && option.key === "priority")
|
if (groupByProperty === "priority" && option.key === "priority")
|
||||||
@ -222,15 +230,19 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Issue type</h4>
|
<h4 className="text-custom-text-200">Issue type</h4>
|
||||||
|
<div className="w-28">
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
label={
|
||||||
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type)
|
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type)
|
||||||
?.name ?? "Select"
|
?.name ?? "Select"
|
||||||
}
|
}
|
||||||
|
className="!w-full"
|
||||||
|
buttonClassName="w-full"
|
||||||
>
|
>
|
||||||
{FILTER_ISSUE_OPTIONS.map((option) => (
|
{FILTER_ISSUE_OPTIONS.map((option) => (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
@ -246,32 +258,23 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
{issueView !== "calendar" && issueView !== "spreadsheet" && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-custom-text-200">Show empty states</h4>
|
<h4 className="text-custom-text-200">Show empty states</h4>
|
||||||
|
<div className="w-28">
|
||||||
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} />
|
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} />
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="relative flex justify-end gap-x-3">
|
</div>
|
||||||
<button type="button" onClick={() => resetFilterToDefault()}>
|
|
||||||
Reset to default
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="font-medium text-custom-primary"
|
|
||||||
onClick={() => setNewFilterDefaultView()}
|
|
||||||
>
|
|
||||||
Set as default
|
|
||||||
</button>
|
|
||||||
</div> */}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
<h4 className="text-sm text-custom-text-200">Display Properties</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2 text-custom-text-200">
|
||||||
{Object.keys(properties).map((key) => {
|
{Object.keys(properties).map((key) => {
|
||||||
if (key === "estimate" && !isEstimateActive) return null;
|
if (key === "estimate" && !isEstimateActive) return null;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import useSWR from "swr";
|
|||||||
import { DropResult } from "react-beautiful-dnd";
|
import { DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
|
import userService from "services/user.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProfileIssues from "hooks/use-profile-issues";
|
import useProfileIssues from "hooks/use-profile-issues";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
@ -19,7 +20,7 @@ import { orderArrayBy } from "helpers/array.helper";
|
|||||||
// types
|
// types
|
||||||
import { IIssue, IIssueFilterOptions } from "types";
|
import { IIssue, IIssueFilterOptions } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_LABELS } from "constants/fetch-keys";
|
import { USER_PROFILE_PROJECT_SEGREGATION, WORKSPACE_LABELS } from "constants/fetch-keys";
|
||||||
|
|
||||||
export const ProfileIssuesView = () => {
|
export const ProfileIssuesView = () => {
|
||||||
// create issue modal
|
// create issue modal
|
||||||
@ -60,6 +61,16 @@ export const ProfileIssuesView = () => {
|
|||||||
params,
|
params,
|
||||||
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
|
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
|
||||||
|
|
||||||
|
const { data: profileData } = useSWR(
|
||||||
|
workspaceSlug && userId
|
||||||
|
? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
||||||
|
: null,
|
||||||
|
workspaceSlug && userId
|
||||||
|
? () =>
|
||||||
|
userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
const { data: labels } = useSWR(
|
const { data: labels } = useSWR(
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
workspaceSlug && (filters?.labels ?? []).length > 0
|
||||||
? WORKSPACE_LABELS(workspaceSlug.toString())
|
? WORKSPACE_LABELS(workspaceSlug.toString())
|
||||||
@ -268,10 +279,10 @@ export const ProfileIssuesView = () => {
|
|||||||
dragDisabled={groupByProperty !== "priority"}
|
dragDisabled={groupByProperty !== "priority"}
|
||||||
emptyState={{
|
emptyState={{
|
||||||
title: router.pathname.includes("assigned")
|
title: router.pathname.includes("assigned")
|
||||||
? `Issues assigned to ${user?.first_name} ${user?.last_name} will appear here`
|
? `Issues assigned to ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here`
|
||||||
: router.pathname.includes("created")
|
: router.pathname.includes("created")
|
||||||
? `Issues created by ${user?.first_name} ${user?.last_name} will appear here`
|
? `Issues created by ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here`
|
||||||
: `Issues subscribed by ${user?.first_name} ${user?.last_name} will appear here`,
|
: `Issues subscribed by ${profileData?.user_data.first_name} ${profileData?.user_data.last_name} will appear here`,
|
||||||
}}
|
}}
|
||||||
handleOnDragEnd={handleOnDragEnd}
|
handleOnDragEnd={handleOnDragEnd}
|
||||||
handleIssueAction={handleIssueAction}
|
handleIssueAction={handleIssueAction}
|
||||||
|
@ -137,7 +137,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`group relative text-custom-sidebar-text-10 px-2 py-1 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
|
className={`group relative text-custom-sidebar-text-10 px-2 py-1 ml-1.5 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
|
||||||
snapshot?.isDragging ? "opacity-60" : ""
|
snapshot?.isDragging ? "opacity-60" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -284,7 +284,7 @@ export const WorkspaceSidebarDropdown = () => {
|
|||||||
onClick={handleSignOut}
|
onClick={handleSignOut}
|
||||||
>
|
>
|
||||||
<Icon iconName="logout" className="!text-base" />
|
<Icon iconName="logout" className="!text-base" />
|
||||||
Log out
|
Sign out
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
@ -12,6 +14,8 @@ import { IIssue } from "types";
|
|||||||
import { USER_ISSUES } from "constants/fetch-keys";
|
import { USER_ISSUES } from "constants/fetch-keys";
|
||||||
|
|
||||||
const useMyIssues = (workspaceSlug: string | undefined) => {
|
const useMyIssues = (workspaceSlug: string | undefined) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { filters, groupBy, orderBy } = useMyIssuesFilters(workspaceSlug);
|
const { filters, groupBy, orderBy } = useMyIssuesFilters(workspaceSlug);
|
||||||
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
@ -27,8 +31,12 @@ const useMyIssues = (workspaceSlug: string | undefined) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { data: myIssues, mutate: mutateMyIssues } = useSWR(
|
const { data: myIssues, mutate: mutateMyIssues } = useSWR(
|
||||||
workspaceSlug ? USER_ISSUES(workspaceSlug.toString(), params) : null,
|
workspaceSlug && router.pathname.includes("my-issues")
|
||||||
workspaceSlug ? () => userService.userIssues(workspaceSlug.toString(), params) : null
|
? USER_ISSUES(workspaceSlug.toString(), params)
|
||||||
|
: null,
|
||||||
|
workspaceSlug && router.pathname.includes("my-issues")
|
||||||
|
? () => userService.userIssues(workspaceSlug.toString(), params)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupedIssues:
|
const groupedIssues:
|
||||||
|
@ -25,6 +25,25 @@
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: light !important;
|
color-scheme: light !important;
|
||||||
|
|
||||||
|
--color-primary-10: 236, 241, 255;
|
||||||
|
--color-primary-20: 217, 228, 255;
|
||||||
|
--color-primary-30: 197, 214, 255;
|
||||||
|
--color-primary-40: 178, 200, 255;
|
||||||
|
--color-primary-50: 159, 187, 255;
|
||||||
|
--color-primary-60: 140, 173, 255;
|
||||||
|
--color-primary-70: 121, 159, 255;
|
||||||
|
--color-primary-80: 101, 145, 255;
|
||||||
|
--color-primary-90: 82, 132, 255;
|
||||||
|
--color-primary-100: 63, 118, 255;
|
||||||
|
--color-primary-200: 57, 106, 230;
|
||||||
|
--color-primary-300: 50, 94, 204;
|
||||||
|
--color-primary-400: 44, 83, 179;
|
||||||
|
--color-primary-500: 38, 71, 153;
|
||||||
|
--color-primary-600: 32, 59, 128;
|
||||||
|
--color-primary-700: 25, 47, 102;
|
||||||
|
--color-primary-800: 19, 35, 76;
|
||||||
|
--color-primary-900: 13, 24, 51;
|
||||||
|
|
||||||
--color-background-100: 255, 255, 255; /* primary bg */
|
--color-background-100: 255, 255, 255; /* primary bg */
|
||||||
--color-background-90: 250, 250, 250; /* secondary bg */
|
--color-background-90: 250, 250, 250; /* secondary bg */
|
||||||
--color-background-80: 245, 245, 245; /* tertiary bg */
|
--color-background-80: 245, 245, 245; /* tertiary bg */
|
||||||
|
25
apps/app/types/issues.d.ts
vendored
25
apps/app/types/issues.d.ts
vendored
@ -38,19 +38,6 @@ export interface IIssueModule {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueCycle {
|
|
||||||
created_at: Date;
|
|
||||||
created_by: string;
|
|
||||||
cycle: string;
|
|
||||||
cycle_detail: ICycle;
|
|
||||||
id: string;
|
|
||||||
issue: string;
|
|
||||||
project: string;
|
|
||||||
updated_at: Date;
|
|
||||||
updated_by: string;
|
|
||||||
workspace: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IIssueParent {
|
export interface IIssueParent {
|
||||||
description: any;
|
description: any;
|
||||||
id: string;
|
id: string;
|
||||||
@ -200,18 +187,26 @@ export interface IIssueActivity {
|
|||||||
created_by: string;
|
created_by: string;
|
||||||
field: string | null;
|
field: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
issue: string;
|
issue: string | null;
|
||||||
issue_comment: string | null;
|
issue_comment: string | null;
|
||||||
|
issue_detail: {
|
||||||
|
description: any;
|
||||||
|
description_html: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
priority: string | null;
|
||||||
|
sequence_id: string;
|
||||||
|
} | null;
|
||||||
new_identifier: string | null;
|
new_identifier: string | null;
|
||||||
new_value: string | null;
|
new_value: string | null;
|
||||||
old_identifier: string | null;
|
old_identifier: string | null;
|
||||||
old_value: string | null;
|
old_value: string | null;
|
||||||
project: string;
|
project: string;
|
||||||
|
project_detail: IProjectLite;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
verb: string;
|
verb: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
workspace_detail: IWorkspaceLite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueComment extends IIssueActivity {
|
export interface IIssueComment extends IIssueActivity {
|
||||||
|
26
apps/app/types/users.d.ts
vendored
26
apps/app/types/users.d.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
IIssue,
|
IIssue,
|
||||||
|
IIssueActivity,
|
||||||
IIssueLite,
|
IIssueLite,
|
||||||
IWorkspace,
|
IWorkspace,
|
||||||
IWorkspaceLite,
|
IWorkspaceLite,
|
||||||
@ -99,29 +100,6 @@ export interface IUserWorkspaceDashboard {
|
|||||||
upcoming_issues: IIssueLite[];
|
upcoming_issues: IIssueLite[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserDetailedActivity {
|
|
||||||
actor: string;
|
|
||||||
actor_detail: IUserLite;
|
|
||||||
attachments: any[];
|
|
||||||
comment: string;
|
|
||||||
created_at: string;
|
|
||||||
created_by: string | null;
|
|
||||||
field: string;
|
|
||||||
id: string;
|
|
||||||
issue: string;
|
|
||||||
issue_comment: string | null;
|
|
||||||
new_identifier: string | null;
|
|
||||||
new_value: string | null;
|
|
||||||
old_identifier: string | null;
|
|
||||||
old_value: string | null;
|
|
||||||
project: string;
|
|
||||||
updated_at: string;
|
|
||||||
updated_by: string | null;
|
|
||||||
verb: string;
|
|
||||||
workspace: string;
|
|
||||||
workspace_detail: IWorkspaceLite;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUserActivityResponse {
|
export interface IUserActivityResponse {
|
||||||
count: number;
|
count: number;
|
||||||
extra_stats: null;
|
extra_stats: null;
|
||||||
@ -129,7 +107,7 @@ export interface IUserActivityResponse {
|
|||||||
next_page_results: boolean;
|
next_page_results: boolean;
|
||||||
prev_cursor: string;
|
prev_cursor: string;
|
||||||
prev_page_results: boolean;
|
prev_page_results: boolean;
|
||||||
results: IUserDetailedActivity[];
|
results: IIssueActivity[];
|
||||||
total_pages: number;
|
total_pages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user