chore: added common component for project activity (#6212)
* chore: added common component for project activity * fix: added enum * fix: added enum for initiatives
This commit is contained in:
parent
8e6d885731
commit
1a715c98b2
7 changed files with 391 additions and 0 deletions
|
|
@ -60,4 +60,6 @@ export enum EFileAssetType {
|
|||
USER_COVER = "USER_COVER",
|
||||
WORKSPACE_LOGO = "WORKSPACE_LOGO",
|
||||
TEAM_SPACE_DESCRIPTION = "TEAM_SPACE_DESCRIPTION",
|
||||
INITIATIVE_DESCRIPTION = "INITIATIVE_DESCRIPTION",
|
||||
PROJECT_DESCRIPTION = "PROJECT_DESCRIPTION",
|
||||
}
|
||||
|
|
|
|||
51
web/core/components/common/activity/activity-block.tsx
Normal file
51
web/core/components/common/activity/activity-block.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"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>
|
||||
);
|
||||
};
|
||||
30
web/core/components/common/activity/activity-item.tsx
Normal file
30
web/core/components/common/activity/activity-item.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { TProjectActivity } from "@/plane-web/types";
|
||||
import { ActivityBlockComponent } from "./activity-block";
|
||||
import { iconsMap, messages } from "./helper";
|
||||
|
||||
type TActivityItem = {
|
||||
activity: TProjectActivity;
|
||||
showProject?: boolean;
|
||||
ends?: "top" | "bottom" | undefined;
|
||||
};
|
||||
|
||||
export const ActivityItem: FC<TActivityItem> = observer((props) => {
|
||||
const { activity, showProject = true, ends } = props;
|
||||
|
||||
if (!activity) return null;
|
||||
|
||||
const activityType = activity.field;
|
||||
const { message, customUserName } = messages(activity);
|
||||
const icon = iconsMap[activityType] || iconsMap.default;
|
||||
|
||||
return (
|
||||
<ActivityBlockComponent icon={icon} activity={activity} ends={ends} customUserName={customUserName}>
|
||||
<>{message}</>
|
||||
</ActivityBlockComponent>
|
||||
);
|
||||
});
|
||||
279
web/core/components/common/activity/helper.tsx
Normal file
279
web/core/components/common/activity/helper.tsx
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
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, " ")} `,
|
||||
};
|
||||
}
|
||||
};
|
||||
1
web/core/components/common/activity/index.ts
Normal file
1
web/core/components/common/activity/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./activity-item";
|
||||
27
web/core/components/common/activity/user.tsx
Normal file
27
web/core/components/common/activity/user.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -5,3 +5,4 @@ export * from "./logo-spinner";
|
|||
export * from "./logo";
|
||||
export * from "./pro-icon";
|
||||
export * from "./count-chip";
|
||||
export * from "./activity";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue