snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
90
apps/web/core/components/profile/overview/activity.tsx
Normal file
90
apps/web/core/components/profile/overview/activity.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar } from "@plane/propel/avatar";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import { Loader, Card } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { ActivityMessage, IssueLink } from "@/components/core/activity";
|
||||
// constants
|
||||
import { USER_PROFILE_ACTIVITY } from "@plane/constants";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
export const ProfileActivity = observer(function ProfileActivity() {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString(), {}) : null,
|
||||
workspaceSlug && userId
|
||||
? () =>
|
||||
userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString(), {
|
||||
per_page: 10,
|
||||
})
|
||||
: null
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-16 font-medium">{t("profile.stats.recent_activity.title")}</h3>
|
||||
<Card>
|
||||
{!userProfileActivity ? (
|
||||
<Loader className="space-y-5">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : Array.isArray(userProfileActivity.results) && userProfileActivity.results.length > 0 ? (
|
||||
<div className="space-y-5">
|
||||
{userProfileActivity.results.map((activity) => (
|
||||
<div key={activity.id} className="flex gap-3">
|
||||
<Avatar
|
||||
name={activity.actor_detail?.display_name}
|
||||
src={getFileURL(activity.actor_detail?.avatar_url)}
|
||||
size="base"
|
||||
shape="square"
|
||||
/>
|
||||
<div className="-mt-1 w-4/5 break-words">
|
||||
<p className="inline text-13 text-secondary">
|
||||
<span className="font-medium text-primary">
|
||||
{currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-11 whitespace-nowrap text-secondary">{calculateTimeAgo(activity.created_at)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyStateCompact title={t("no_data_yet")} assetKey="unknown" assetClassName="size-20" />
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { BarChart } from "@plane/propel/charts/bar-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IUserProfileData } from "@plane/types";
|
||||
import { Loader, Card } from "@plane/ui";
|
||||
import { capitalizeFirstLetter } from "@plane/utils";
|
||||
|
||||
type Props = {
|
||||
userProfile: IUserProfileData | undefined;
|
||||
};
|
||||
|
||||
const priorityColors = {
|
||||
urgent: "#991b1b",
|
||||
high: "#ef4444",
|
||||
medium: "#f59e0b",
|
||||
low: "#16a34a",
|
||||
none: "#e5e5e5",
|
||||
};
|
||||
|
||||
export function ProfilePriorityDistribution({ userProfile }: Props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-16 font-medium">{t("profile.stats.priority_distribution.title")}</h3>
|
||||
{userProfile ? (
|
||||
<Card>
|
||||
{userProfile.priority_distribution.length > 0 ? (
|
||||
<BarChart
|
||||
className="h-[300px] w-full"
|
||||
margin={{ top: 20, right: 30, bottom: 5, left: 0 }}
|
||||
data={userProfile.priority_distribution.map((priority) => ({
|
||||
key: priority.priority ?? "None",
|
||||
name: capitalizeFirstLetter(priority.priority ?? "None"),
|
||||
count: priority.priority_count,
|
||||
}))}
|
||||
bars={[
|
||||
{
|
||||
key: "count",
|
||||
label: "Count",
|
||||
stackId: "bar-one",
|
||||
fill: (payload: any) => priorityColors[payload.key as keyof typeof priorityColors], // TODO: fix types
|
||||
textClassName: "",
|
||||
showPercentage: false,
|
||||
showTopBorderRadius: () => true,
|
||||
showBottomBorderRadius: () => true,
|
||||
},
|
||||
]}
|
||||
xAxis={{
|
||||
key: "name",
|
||||
label: t("common.priority"),
|
||||
}}
|
||||
yAxis={{
|
||||
key: "count",
|
||||
label: "",
|
||||
}}
|
||||
barSize={20}
|
||||
/>
|
||||
) : (
|
||||
<EmptyStateCompact
|
||||
assetKey="priority"
|
||||
assetClassName="size-20"
|
||||
title={t("workspace_empty_state.your_work_by_priority.title")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid place-items-center p-7">
|
||||
<Loader className="flex items-end gap-12">
|
||||
<Loader.Item width="30px" height="200px" />
|
||||
<Loader.Item width="30px" height="150px" />
|
||||
<Loader.Item width="30px" height="250px" />
|
||||
<Loader.Item width="30px" height="150px" />
|
||||
<Loader.Item width="30px" height="100px" />
|
||||
</Loader>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import { STATE_GROUPS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PieChart } from "@plane/propel/charts/pie-chart";
|
||||
import { EmptyStateCompact } from "@plane/propel/empty-state";
|
||||
import type { IUserProfileData, IUserStateDistribution } from "@plane/types";
|
||||
import { Card } from "@plane/ui";
|
||||
import { capitalizeFirstLetter } from "@plane/utils";
|
||||
|
||||
type Props = {
|
||||
stateDistribution: IUserStateDistribution[];
|
||||
userProfile: IUserProfileData | undefined;
|
||||
};
|
||||
|
||||
export function ProfileStateDistribution({ stateDistribution, userProfile }: Props) {
|
||||
const { t } = useTranslation();
|
||||
if (!userProfile) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-16 font-medium">{t("profile.stats.state_distribution.title")}</h3>
|
||||
<Card className="h-full">
|
||||
{userProfile.state_distribution.length > 0 ? (
|
||||
<div className="grid h-[300px] w-full grid-cols-1 gap-x-6 md:grid-cols-2">
|
||||
<PieChart
|
||||
className="size-full"
|
||||
dataKey="value"
|
||||
margin={{
|
||||
top: 0,
|
||||
right: -10,
|
||||
bottom: 12,
|
||||
left: -10,
|
||||
}}
|
||||
data={
|
||||
userProfile.state_distribution.map((group) => ({
|
||||
id: group.state_group,
|
||||
key: group.state_group,
|
||||
value: group.state_count,
|
||||
name: capitalizeFirstLetter(group.state_group),
|
||||
color: STATE_GROUPS[group.state_group]?.color,
|
||||
})) ?? []
|
||||
}
|
||||
cells={userProfile.state_distribution.map((group) => ({
|
||||
key: group.state_group,
|
||||
fill: STATE_GROUPS[group.state_group]?.color,
|
||||
}))}
|
||||
showTooltip
|
||||
tooltipLabel="Count"
|
||||
paddingAngle={5}
|
||||
cornerRadius={4}
|
||||
innerRadius="50%"
|
||||
showLabel={false}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<div className="w-full space-y-4">
|
||||
{stateDistribution.map((group) => (
|
||||
<div key={group.state_group} className="flex items-center justify-between gap-2 text-11">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="h-2.5 w-2.5 rounded-xs"
|
||||
style={{
|
||||
backgroundColor:
|
||||
STATE_GROUPS[group.state_group]?.color ?? "var(--background-color-accent-primary)",
|
||||
}}
|
||||
/>
|
||||
<div className="whitespace-nowrap">{STATE_GROUPS[group.state_group].label}</div>
|
||||
</div>
|
||||
<div>{group.state_count}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyStateCompact
|
||||
assetKey="priority"
|
||||
assetClassName="size-20"
|
||||
title={t("workspace_empty_state.your_work_by_priority.title")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
apps/web/core/components/profile/overview/stats.tsx
Normal file
76
apps/web/core/components/profile/overview/stats.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { LayerStackIcon } from "@plane/propel/icons";
|
||||
import { AddOutline, UserOutline } from "@makeplane/propel/icons";
|
||||
import type { IUserProfileData } from "@plane/types";
|
||||
import { Loader, Card, ECardSpacing, ECardDirection } from "@plane/ui";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
userProfile: IUserProfileData | undefined;
|
||||
};
|
||||
|
||||
export function ProfileStats({ userProfile }: Props) {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const overviewCards = [
|
||||
{
|
||||
icon: AddOutline,
|
||||
route: "created",
|
||||
i18n_title: "profile.stats.created",
|
||||
value: userProfile?.created_issues ?? "...",
|
||||
},
|
||||
{
|
||||
icon: UserOutline,
|
||||
route: "assigned",
|
||||
i18n_title: "profile.stats.assigned",
|
||||
value: userProfile?.assigned_issues ?? "...",
|
||||
},
|
||||
{
|
||||
icon: LayerStackIcon,
|
||||
route: "subscribed",
|
||||
i18n_title: "profile.stats.subscribed",
|
||||
value: userProfile?.subscribed_issues ?? "...",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-16 font-medium">{t("profile.stats.overview")}</h3>
|
||||
{userProfile ? (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{overviewCards.map((card) => (
|
||||
<Link key={card.route} href={`/${workspaceSlug}/profile/${userId}/${card.route}`}>
|
||||
<Card direction={ECardDirection.ROW} spacing={ECardSpacing.SM} className="h-full">
|
||||
<div className="grid h-11 w-11 place-items-center rounded-sm bg-surface-2">
|
||||
<card.icon className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-13 text-placeholder">{t(card.i18n_title)}</p>
|
||||
<p className="text-18 font-semibold">{card.value}</p>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Loader className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Loader.Item height="80px" />
|
||||
<Loader.Item height="80px" />
|
||||
<Loader.Item height="80px" />
|
||||
</Loader>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
apps/web/core/components/profile/overview/workload.tsx
Normal file
53
apps/web/core/components/profile/overview/workload.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// plane imports
|
||||
import { STATE_GROUPS } from "@plane/constants";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IUserStateDistribution } from "@plane/types";
|
||||
import { Card, ECardDirection, ECardSpacing } from "@plane/ui";
|
||||
// constants
|
||||
|
||||
type Props = {
|
||||
stateDistribution: IUserStateDistribution[];
|
||||
};
|
||||
|
||||
export function ProfileWorkload({ stateDistribution }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-16 font-medium">{t("profile.stats.workload")}</h3>
|
||||
<div className="grid grid-cols-1 justify-stretch gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||||
{stateDistribution.map((group) => (
|
||||
<div key={group.state_group}>
|
||||
<a>
|
||||
<Card direction={ECardDirection.ROW} spacing={ECardSpacing.SM}>
|
||||
<div
|
||||
className="my-2 h-3 w-3 rounded-xs"
|
||||
style={{
|
||||
backgroundColor: STATE_GROUPS[group.state_group].color,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-col space-y-1">
|
||||
<span className="text-13 text-placeholder">
|
||||
{group.state_group === "unstarted"
|
||||
? "Not started"
|
||||
: group.state_group === "started"
|
||||
? "Working on"
|
||||
: STATE_GROUPS[group.state_group].label}
|
||||
</span>
|
||||
<p className="text-18 font-semibold">{group.state_count}</p>
|
||||
</div>
|
||||
</Card>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user