feat: init
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
chuan
2025-11-11 01:56:44 +08:00
commit bba4bb40c8
4638 changed files with 447437 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { ProjectIcon } from "@plane/propel/icons";
// plane package imports
import { cn } from "@plane/utils";
import { Logo } from "@/components/common/logo";
// plane web hooks
import { useProject } from "@/hooks/store/use-project";
type Props = {
project: {
id: string;
completed_issues?: number;
total_issues?: number;
};
isLoading?: boolean;
};
const CompletionPercentage = ({ percentage }: { percentage: number }) => {
const percentageColor = percentage > 50 ? "bg-green-500/30 text-green-500" : "bg-red-500/30 text-red-500";
return (
<div className={cn("flex items-center gap-2 rounded p-1 text-xs", percentageColor)}>
<span>{percentage}%</span>
</div>
);
};
const ActiveProjectItem = (props: Props) => {
const { project } = props;
const { getProjectById } = useProject();
const { id, completed_issues, total_issues } = project;
const projectDetails = getProjectById(id);
if (!projectDetails) return null;
return (
<div className="flex items-center justify-between gap-2 ">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-xl bg-custom-background-80">
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
{projectDetails?.logo_props ? (
<Logo logo={projectDetails?.logo_props} size={16} />
) : (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectIcon className="h-4 w-4" />
</span>
)}
</span>
</div>
<p className="text-sm font-medium">{projectDetails?.name}</p>
</div>
<CompletionPercentage
percentage={completed_issues && total_issues ? Math.round((completed_issues / total_issues) * 100) : 0}
/>
</div>
);
};
export default ActiveProjectItem;

View File

@@ -0,0 +1,45 @@
import React from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane package imports
import { useTranslation } from "@plane/i18n";
import { Loader } from "@plane/ui";
// plane web hooks
import { useAnalytics } from "@/hooks/store/use-analytics";
import { useProject } from "@/hooks/store/use-project";
// plane web components
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import ActiveProjectItem from "./active-project-item";
const ActiveProjects = observer(() => {
const { t } = useTranslation();
const { fetchProjectAnalyticsCount } = useProject();
const { workspaceSlug } = useParams();
const { selectedDurationLabel } = useAnalytics();
const { data: projectAnalyticsCount, isLoading: isProjectAnalyticsCountLoading } = useSWR(
workspaceSlug ? ["projectAnalyticsCount", workspaceSlug] : null,
workspaceSlug
? () =>
fetchProjectAnalyticsCount(workspaceSlug.toString(), {
fields: "total_work_items,total_completed_work_items",
})
: null
);
return (
<AnalyticsSectionWrapper
title={`${t("workspace_analytics.active_projects")}`}
subtitle={selectedDurationLabel}
className="md:col-span-2"
>
<div className="flex flex-col gap-4 h-[350px] overflow-auto">
{isProjectAnalyticsCountLoading &&
Array.from({ length: 5 }).map((_, index) => <Loader.Item key={index} height="40px" width="100%" />)}
{!isProjectAnalyticsCountLoading &&
projectAnalyticsCount?.map((project) => <ActiveProjectItem key={project.id} project={project} />)}
</div>
</AnalyticsSectionWrapper>
);
});
export default ActiveProjects;

View File

@@ -0,0 +1 @@
export * from "./root";

View File

@@ -0,0 +1,117 @@
import { observer } from "mobx-react";
import dynamic from "next/dynamic";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane package imports
import { useTranslation } from "@plane/i18n";
import type { TChartData } from "@plane/types";
// hooks
import { useAnalytics } from "@/hooks/store/use-analytics";
// services
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { AnalyticsService } from "@/services/analytics.service";
// plane web components
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import AnalyticsEmptyState from "../empty-state";
import { ProjectInsightsLoader } from "../loaders";
const RadarChart = dynamic(() =>
import("@plane/propel/charts/radar-chart").then((mod) => ({
default: mod.RadarChart,
}))
);
const analyticsService = new AnalyticsService();
const ProjectInsights = observer(() => {
const params = useParams();
const { t } = useTranslation();
const workspaceSlug = params.workspaceSlug.toString();
const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } =
useAnalytics();
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-radar" });
const { data: projectInsightsData, isLoading: isLoadingProjectInsight } = useSWR(
`radar-chart-project-insights-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`,
() =>
analyticsService.getAdvanceAnalyticsCharts<TChartData<string, string>[]>(
workspaceSlug,
"projects",
{
// date_filter: selectedDuration,
...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }),
...(selectedCycle ? { cycle_id: selectedCycle } : {}),
...(selectedModule ? { module_id: selectedModule } : {}),
},
isPeekView
)
);
return (
<AnalyticsSectionWrapper
title={`${t("workspace_analytics.project_insights")}`}
subtitle={selectedDurationLabel}
className="md:col-span-3"
>
{isLoadingProjectInsight ? (
<ProjectInsightsLoader />
) : projectInsightsData && projectInsightsData?.length == 0 ? (
<AnalyticsEmptyState
title={t("workspace_analytics.empty_state.project_insights.title")}
description={t("workspace_analytics.empty_state.project_insights.description")}
className="h-[300px]"
assetPath={resolvedPath}
/>
) : (
<div className="gap-8 lg:flex">
{projectInsightsData && (
<RadarChart
className="h-[350px] w-full lg:w-3/5"
data={projectInsightsData}
dataKey="key"
radars={[
{
key: "count",
name: "Count",
fill: "rgba(var(--color-primary-300))",
stroke: "rgba(var(--color-primary-300))",
fillOpacity: 0.6,
dot: {
r: 4,
fillOpacity: 1,
},
},
]}
margin={{ top: 0, right: 40, bottom: 10, left: 40 }}
showTooltip
angleAxis={{
key: "name",
}}
/>
)}
<div className="w-full lg:w-2/5">
<div className="text-sm text-custom-text-300">{t("workspace_analytics.summary_of_projects")}</div>
<div className=" mb-3 border-b border-custom-border-100 py-2">{t("workspace_analytics.all_projects")}</div>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between text-sm text-custom-text-300">
<div>{t("workspace_analytics.trend_on_charts")}</div>
<div>{t("common.work_items")}</div>
</div>
{projectInsightsData?.map((item) => (
<div key={item.key} className="flex items-center justify-between text-sm text-custom-text-100">
<div>{item.name}</div>
<div className="flex items-center gap-1">
{/* <TrendPiece key={item.key} size='xs' /> */}
<div className="text-custom-text-200">{item.count}</div>
</div>
</div>
))}
</div>
</div>
</div>
)}
</AnalyticsSectionWrapper>
);
});
export default ProjectInsights;

View File

@@ -0,0 +1,19 @@
import React from "react";
import AnalyticsWrapper from "../analytics-wrapper";
import TotalInsights from "../total-insights";
import ActiveProjects from "./active-projects";
import ProjectInsights from "./project-insights";
const Overview: React.FC = () => (
<AnalyticsWrapper i18nTitle="common.overview">
<div className="flex flex-col gap-14">
<TotalInsights analyticsType="overview" />
<div className="grid grid-cols-1 gap-14 md:grid-cols-5 ">
<ProjectInsights />
<ActiveProjects />
</div>
</div>
</AnalyticsWrapper>
);
export { Overview };