fix: integrated common activity componnet (#1950)

This commit is contained in:
Akshita Goyal
2024-12-17 17:32:46 +05:30
committed by GitHub
parent 4d67404f71
commit ea0f897cfc
6 changed files with 35 additions and 413 deletions

View File

@@ -1,51 +0,0 @@
"use client";
import { FC, ReactNode } from "react";
import { Network } from "lucide-react";
// hooks
import { Tooltip } from "@plane/ui";
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper";
import { usePlatformOS } from "@/hooks/use-platform-os";
import { TProjectActivity } from "@/plane-web/types";
import { User } from "./user";
type TActivityBlockComponent = {
icon?: ReactNode;
activity: TProjectActivity;
ends: "top" | "bottom" | undefined;
children: ReactNode;
customUserName?: string;
};
export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
const { icon, activity, ends, children, customUserName } = props;
// hooks
const { isMobile } = usePlatformOS();
if (!activity) return <></>;
return (
<div
className={`relative flex items-center gap-3 text-xs ${
ends === "top" ? `pb-2` : ends === "bottom" ? `pt-2` : `py-2`
}`}
>
<div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
<div className="flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden flex justify-center items-center z-[4] bg-custom-background-80 text-custom-text-200">
{icon ? icon : <Network className="w-3.5 h-3.5" />}
</div>
<div className="w-full truncate text-custom-text-200">
<User activity={activity} customUserName={customUserName} /> {children}
<div className="mt-1">
<Tooltip
isMobile={isMobile}
tooltipContent={`${renderFormattedDate(activity.created_at)}, ${renderFormattedTime(activity.created_at)}`}
>
<span className="whitespace-nowrap text-custom-text-350 font-medium">
{calculateTimeAgo(activity.created_at)}
</span>
</Tooltip>
</div>
</div>
</div>
);
};

View File

@@ -1,27 +0,0 @@
import { FC } from "react";
import Link from "next/link";
import { TProjectActivity } from "@/plane-web/types";
type TUser = {
activity: TProjectActivity;
customUserName?: string;
};
export const User: FC<TUser> = (props) => {
const { activity, customUserName } = props;
return (
<>
{customUserName || activity.actor_detail?.display_name.includes("-intake") ? (
<span className="text-custom-text-100 font-medium">{customUserName || "Plane"}</span>
) : (
<Link
href={`/${activity?.workspace_detail?.slug}/profile/${activity?.actor_detail?.id}`}
className="hover:underline text-custom-text-100 font-medium"
>
{activity.actor_detail?.display_name}
</Link>
)}
</>
);
};

View File

@@ -1,51 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react";
// components
import useSWR from "swr";
import { Loader } from "@plane/ui";
import { ActivityItem } from "@/plane-web/components/common/activity/actions/helpers/activity-item";
import { ProjectActivityService } from "@/plane-web/services";
import { iconsMap, messages } from "./helper";
const projectActivityService = new ProjectActivityService();
type TProjectActivityCommentRoot = {
workspaceSlug: string;
projectId: string;
};
export const ProjectActivityCommentRoot: FC<TProjectActivityCommentRoot> = observer((props) => {
const { workspaceSlug, projectId } = props;
// api calls
const { data: activity, isLoading } = useSWR(
projectId && workspaceSlug ? `PROJECT_ACTIVITY_${projectId}` : null,
projectId && workspaceSlug ? () => projectActivityService.getProjectActivities(workspaceSlug, projectId) : null,
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return isLoading ? (
<Loader className="space-y-3">
<Loader.Item height="34px" width="100%" />
<Loader.Item height="34px" width="100%" />
<Loader.Item height="34px" width="100%" />
</Loader>
) : (
<div>
{activity &&
activity.map((activityComment, index) => (
<ActivityItem
key={activityComment.id}
activity={activityComment}
ends={index === 0 ? "top" : index === activity.length - 1 ? "bottom" : undefined}
iconsMap={iconsMap}
messages={messages}
/>
))}
</div>
);
});

View File

@@ -1,279 +0,0 @@
import { ReactNode } from "react";
import {
Signal,
RotateCcw,
Network,
Link as LinkIcon,
Calendar,
Tag,
Inbox,
AlignLeft,
Users,
Paperclip,
Type,
Triangle,
FileText,
Globe,
Hash,
Clock,
Bell,
LayoutGrid,
GitBranch,
Timer,
ListTodo,
Layers,
} from "lucide-react";
// components
import { ArchiveIcon, DoubleCircleIcon, ContrastIcon, DiceIcon, Intake } from "@plane/ui";
import { TProjectActivity } from "@/plane-web/types";
type ActivityIconMap = {
[key: string]: ReactNode;
};
export const iconsMap: ActivityIconMap = {
priority: <Signal size={14} className="text-custom-text-200" />,
archived_at: <ArchiveIcon className="h-3.5 w-3.5 text-custom-text-200" />,
restored: <RotateCcw className="h-3.5 w-3.5 text-custom-text-200" />,
link: <LinkIcon className="h-3.5 w-3.5 text-custom-text-200" />,
start_date: <Calendar className="h-3.5 w-3.5 text-custom-text-200" />,
target_date: <Calendar className="h-3.5 w-3.5 text-custom-text-200" />,
label: <Tag className="h-3.5 w-3.5 text-custom-text-200" />,
inbox: <Inbox className="h-3.5 w-3.5 text-custom-text-200" />,
description: <AlignLeft className="h-3.5 w-3.5 text-custom-text-200" />,
assignee: <Users className="h-3.5 w-3.5 text-custom-text-200" />,
attachment: <Paperclip className="h-3.5 w-3.5 text-custom-text-200" />,
name: <Type className="h-3.5 w-3.5 text-custom-text-200" />,
state: <DoubleCircleIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
estimate: <Triangle size={14} className="text-custom-text-200" />,
cycle: <ContrastIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
module: <DiceIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
page: <FileText className="h-3.5 w-3.5 text-custom-text-200" />,
network: <Globe className="h-3.5 w-3.5 text-custom-text-200" />,
identifier: <Hash className="h-3.5 w-3.5 text-custom-text-200" />,
timezone: <Clock className="h-3.5 w-3.5 text-custom-text-200" />,
is_project_updates_enabled: <Bell className="h-3.5 w-3.5 text-custom-text-200" />,
is_epic_enabled: <LayoutGrid className="h-3.5 w-3.5 text-custom-text-200" />,
is_workflow_enabled: <GitBranch className="h-3.5 w-3.5 text-custom-text-200" />,
is_time_tracking_enabled: <Timer className="h-3.5 w-3.5 text-custom-text-200" />,
is_issue_type_enabled: <ListTodo className="h-3.5 w-3.5 text-custom-text-200" />,
default: <Network className="h-3.5 w-3.5 text-custom-text-200" />,
module_view: <DiceIcon className="h-3.5 w-3.5 text-custom-text-200" />,
cycle_view: <ContrastIcon className="h-3.5 w-3.5 text-custom-text-200" />,
issue_views_view: <Layers className="h-3.5 w-3.5 text-custom-text-200" />,
page_view: <FileText className="h-3.5 w-3.5 text-custom-text-200" />,
intake_view: <Intake className="h-3.5 w-3.5 text-custom-text-200" />,
};
export const messages = (activity: TProjectActivity): { message: string | ReactNode; customUserName?: string } => {
const activityType = activity.field;
const newValue = activity.new_value;
const oldValue = activity.old_value;
const verb = activity.verb;
const getBooleanActionText = (value: string) => {
if (value === "true") return "enabled";
if (value === "false") return "disabled";
return verb;
};
switch (activityType) {
case "priority":
return {
message: (
<>
set the priority to <span className="font-medium text-custom-text-100">{newValue || "none"}</span>
</>
),
};
case "archived_at":
return {
message: newValue === "restore" ? "restored the project" : "archived the project",
customUserName: newValue === "archive" ? "Plane" : undefined,
};
case "name":
return {
message: (
<>
renamed the project to <span className="font-medium text-custom-text-100">{newValue}</span>
</>
),
};
case "description":
return {
message: newValue ? "updated the project description" : "removed the project description",
};
case "start_date":
return {
message: (
<>
{newValue ? (
<>
set the start date to <span className="font-medium text-custom-text-100">{newValue}</span>
</>
) : (
"removed the start date"
)}
</>
),
};
case "target_date":
return {
message: (
<>
{newValue ? (
<>
set the target date to <span className="font-medium text-custom-text-100">{newValue}</span>
</>
) : (
"removed the target date"
)}
</>
),
};
case "state":
return {
message: (
<>
set the state to <span className="font-medium text-custom-text-100">{newValue || "none"}</span>
</>
),
};
case "estimate":
return {
message: (
<>
{newValue ? (
<>
set the estimate point to <span className="font-medium text-custom-text-100">{newValue}</span>
</>
) : (
<>
removed the estimate point
{oldValue && (
<>
{" "}
<span className="font-medium text-custom-text-100">{oldValue}</span>
</>
)}
</>
)}
</>
),
};
case "cycles":
return {
message: (
<>
<span>
{verb} this project {verb === "removed" ? "from" : "to"} the cycle{" "}
</span>
{verb !== "removed" ? (
<a
href={`/${activity.workspace_detail?.slug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex font-medium text-custom-text-100"
>
{activity.new_value}
</a>
) : (
<span className="font-medium text-custom-text-100">{activity.old_value || "Unknown cycle"}</span>
)}
</>
),
};
case "modules":
return {
message: (
<>
<span>
{verb} this project {verb === "removed" ? "from" : "to"} the module{" "}
</span>
<span className="font-medium text-custom-text-100">
{verb === "removed" ? oldValue : newValue || "Unknown module"}
</span>
</>
),
};
case "labels":
return {
message: (
<>
{verb} the label{" "}
<span className="font-medium text-custom-text-100">{newValue || oldValue || "Untitled label"}</span>
</>
),
};
case "inbox":
return {
message: <>{newValue ? "enabled" : "disabled"} inbox</>,
};
case "page":
return {
message: (
<>
{newValue ? "created" : "removed"} the project page{" "}
<span className="font-medium text-custom-text-100">{newValue || oldValue || "Untitled page"}</span>
</>
),
};
case "network":
return {
message: <>{newValue ? "enabled" : "disabled"} network access</>,
};
case "identifier":
return {
message: (
<>
updated project identifier to <span className="font-medium text-custom-text-100">{newValue || "none"}</span>
</>
),
};
case "timezone":
return {
message: (
<>
changed project timezone to{" "}
<span className="font-medium text-custom-text-100">{newValue || "default"}</span>
</>
),
};
case "module_view":
case "cycle_view":
case "issue_views_view":
case "page_view":
case "intake_view":
return {
message: (
<>
{getBooleanActionText(newValue)} {activityType.replace(/_view$/, "").replace(/_/g, " ")} view
</>
),
};
case "is_project_updates_enabled":
return {
message: <>{getBooleanActionText(newValue)} project updates</>,
};
case "is_epic_enabled":
return {
message: <>{getBooleanActionText(newValue)} epics</>,
};
case "is_workflow_enabled":
return {
message: <>{getBooleanActionText(newValue)} custom workflow</>,
};
case "is_time_tracking_enabled":
return {
message: <>{getBooleanActionText(newValue)} time tracking</>,
};
case "is_issue_type_enabled":
return {
message: <>{getBooleanActionText(newValue)} issue types</>,
};
default:
return {
message: `${verb} ${activityType.replace(/_/g, " ")} `,
};
}
};

View File

@@ -1,3 +1 @@
export * from "./root";
export * from "./activity-comment-root";

View File

@@ -1,8 +1,11 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react";
import { ProjectActivityCommentRoot } from "./activity-comment-root";
// components
import useSWR from "swr";
import { Loader } from "@plane/ui";
import { ProjectActivityService } from "@/plane-web/services";
import { ActivityItem } from "@/components/common";
// components
type TProjectActivity = {
@@ -10,8 +13,20 @@ type TProjectActivity = {
projectId: string;
};
const projectActivityService = new ProjectActivityService();
export const ProjectActivity: FC<TProjectActivity> = observer((props) => {
const { workspaceSlug, projectId } = props;
// api calls
const { data: activity, isLoading } = useSWR(
projectId && workspaceSlug ? `PROJECT_ACTIVITY_${projectId}` : null,
projectId && workspaceSlug ? () => projectActivityService.getProjectActivities(workspaceSlug, projectId) : null,
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return (
<div className="space-y-4 pt-3 pb-20">
@@ -19,7 +34,24 @@ export const ProjectActivity: FC<TProjectActivity> = observer((props) => {
<div className="space-y-3">
<div className="min-h-[200px]">
<div className="space-y-3">
<ProjectActivityCommentRoot projectId={projectId} workspaceSlug={workspaceSlug} />
{isLoading ? (
<Loader className="space-y-3">
<Loader.Item height="34px" width="100%" />
<Loader.Item height="34px" width="100%" />
<Loader.Item height="34px" width="100%" />
</Loader>
) : (
<div>
{activity &&
activity.map((activityComment, index) => (
<ActivityItem
key={activityComment.id}
activity={activityComment}
ends={index === 0 ? "top" : index === activity.length - 1 ? "bottom" : undefined}
/>
))}
</div>
)}
</div>
</div>
</div>